Can't invoke single query GET in WebApi OData - c#

I have web api with OData controller. Models is
public class UserDto
{
public int UserDtoId {get;set;}
public string Name {get;set;}
}
in controller I have two method
[EnableQuery]
public IQueryable<UserDto> Get();
[EnableQuery]
public SingleResult<UserDto> GetUser([FromODataUri] int key);
OData config is:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<UserDto>("Users").EntityType.HasKey(e=>e.UserDtoId).Name = "User";
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
when I try invoke odata/Users(123), the odata try to invoke first get not a get with a key and return me all record from table. When I comment out first get method there is none GET method for this URI access at all. Where I make a mistake?
I try to make [ODataRoute] its doesnt change nothing.

In your code, your two functions are Get() and GetUser(int key)
They should both be 'Get'
Also not sure if it matters, but for the Get(int) one, if it still doesnt work, try changing SingleResult<UserDto> to just UserDto

Related

Hide action parameters with [BindNever] attribute from swagger

I want to hide parameters with the BindNever attribute in actions from SwaggerUI.
My action:
[HttpGet("{id:int}")]
[TryGetUserByIdValidation(GetAsUserReadDto = true, UserArgumentName = "userDto")]
public virtual ActionResult<ApiResult<User>> Get(int id,
[BindNever] UserReadDto userDto)
{
var res = new ApiResult<UserReadDto>()
.WithData(userDto);
return Ok(res);
}
In SwaggerUI I can pass the [BindNever] UserReadDto userDto as JSON in the request body, but the method is GET so I get an error in swaggerUI.
Why do I have a BindNever parameter in my action? I'm filling it with TryGetUserByIdValidation ActionFilter which is above my action. It handles all the mappings and errors. I'm preventing binding for that parameter with BindNever but it is not fully compatible with API so sometimes I have to use it with FromForm or any From... attribute except FromBody.
I have tried this OperationFilter.
public class SwaggerExcludeBindNeverFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var paramsToHide = context.MethodInfo.GetParameters()
.Where(para => para.CustomAttributes.Any(att => att.AttributeType == typeof(BindNeverAttribute)))
.ToList();
if (paramsToHide.Any())
{
// What to do now?
operation.RequestBody.Content.Clear();
}
}
}
And add to swagger:
options.OperationFilter<SwaggerExcludeBindNeverFilter>();
But this code removes all RequestBody.Content and as I said it can be in Form or anywhere else.
Swagger should not send or even show this.
You can tell me which attribute should I use to prevent Binding and show it in swagger.
I'm using Asp.Net Core 5.
In the case that I can't add a service to HttpContext.RequestServices after a request was made and get it in action parameter with [FromServices] attribute (error: this service is not registered).
So I pass the data with the HttpContext.Items[] dictionary.
In TryGetUserByIdValidation ActionFilter:
// pervious code
// context.ActionArguments[UserArgumentName] = user;
context.HttpContext.Items[ItemKey] = user; // < set the item
Action:
[HttpGet("{id:int}")]
[TryGetUserByIdValidation(GetAsUserReadDto = true, ItemKey = "userDto")]
public virtual ActionResult<ApiResult<User>> Get(int id)
{
UserReadDto userDto = HttpContext.Items[nameof(userDto)] as UserReadDto; // < get the item
var res = new ApiResult<UserReadDto>()
.WithData(userDto);
return Ok(res);
}
This is just a whitewash answer, it works for me. It does not have anything to do with Swagger so please post an answer to hide [BindNever] action parameters. To accept that.

How do I bind multiple entity sets to one odata controller?

I have a lot of different entities that I want to enable OData for. These entities are categorized into different groups based on their type. Currently, the default is to match the EntitySet with the controller name, but I don't want a controller for every entity type that I'll have. Is there a way I can map multiple EntitySets to one controller.
I've tried having the types I'm interested in implement a common interface and specified that interface as my entity set type. I also tried having two entities within one controller and with their own get requests, but no luck. I also tried defining my own routing class that extends EntitySetRoutingConvention, but haven't gotten that to work.
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyEntity1>("MyEntity1");
builder.EntitySet<MyEntity2>("MyEntity2");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
// Web API routes
config.MapHttpAttributeRoutes();
}
This looks for controllers named MyEntity1Controller and MyEntity2Controller.
What I want is something like:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyEntity1>("Generic");
builder.EntitySet<MyEntity2>("Generic"); // Throws an error since Generic is already registered to MyEntity1
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
// Web API routes
config.MapHttpAttributeRoutes();
}
GenericController.cs
// GET: odata/myentity1
[EnableQuery]
public IQueryable<MyEntity1> GetMyEntity1()
{
return db.MyEntity1.AsQueryable();
}
// GET: odata/myentity2
[EnableQuery]
public IQueryable<myentity2> GetMyEntity2()
{
return db.MyEntity2.AsQueryable();
}
The expected results would be I can go to myurl/Generic/MyEntity1 and that would hit a GET request in my Generic Controller. I should also be able to perform odata operations such as myurl/Generic/MyEntity1?$select=Id.
Add the ODataRoute Attribute
/// MyController.cs
// GET: odata/myentity1
[EnableQuery]
[ODataRoute("myentity1")]
public IQueryable<MyEntity1> GetMyEntity1() => db.MyEntity1.AsQueryable();
// GET: odata/myentity2
[EnableQuery]
[ODataRoute("myentity1")]
public IQueryable<MyEntity2> GetMyEntity2() => db.MyEntity2.AsQueryable();

OData, method get with key not found

With the code below, I can hit (using Fiddler):
GetCustomers via GET: odata/Customers
Post(CustomerModel customer) via POST: odata/Customers
Delete via DELETE: odata/Customers(5)
The delete method look like :
public IHttpActionResult Delete([FromODataUri] int key)
{
Console.WriteLine(key);
}
I hit the method and I get the key, no problem.
But I don't hit the get method with the key (no problem with the get method without the key, I get the full list) :
// GET: odata/Customers(5)
public IHttpActionResult GetCustomer([FromODataUri] int key)
{
Console.WriteLine(key);
}
I get this error (Response headers via Fiddler):
HTTP/1.1 404 Not Found
The WebApiConfig is :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<CustomerModel>("Customers");
builder.EntitySet<EmployeeModel>("Employees");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel());
}
}
The method name needs to be Get to be picked up by the OData routing:
Get([FromODataUri] int key)
By Web API OData convention, it should support the following two rules:
HttpMethodName + entityTypeName
HttpMethodName
Convention #1 has high priority than convention #2.
Based on the conventions, you will get 404-NotFound if you only define the following actions in the controller:
GetCustomer([FromODataUri] int key)
GetCustomers([FromODataUri] int key)
Otherwise, it should work if you define at least one of the following actions in the controller:
GetCustomerModel([FromODataUri] int key)
Get([FromODataUri] int key)
https://learn.microsoft.com/en-gb/odata/webapi/built-in-routing-conventions lists the routing conventions used in Web API OData. Hope it can help you. Thanks.

$Metadata with WebAPi OData Attribute Routing Not Working

I'm using OData Attribute Routing for an OData endpoint. Here is an example of what I have:
[ODataRoutePrefix("Profile")]
public class ProfileODataController : ODataController
{
[ODataRoute]
[EnableQuery]
public IHttpActionResult Get()
{
var repo = new Repositories.ProfileRepository();
return Ok(repo.GetProfiles());
}
[ODataRoute("({key})")]
[EnableQuery]
public IHttpActionResult Get([FromODataUri] string key)
{
var repo = new Repositories.ProfileRepository();
var result = repo.GetProfiles().SingleOrDefault(x => x.Id== key);
if (result == null) return NotFound();
return Ok(result);
}
}
Here is my set up:
config.MapODataServiceRoute("odata", "odata", ModelGenerator.GetEdmModel());
Here is my EdmModel Generation:
public static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Profile>("Profile").EntityType.HasKey(x => x.Id);
return builder.GetEdmModel();
}
The urls /odata/Profile and /odata/Profile('someid') both work, but when I try to access the $metadata endpoint (/odata/$metadata#Profile), I get the following error:
{"Message":"No HTTP resource was found that matches the request URI 'http://****/odata/$metadata'.","MessageDetail":"No type was found that matches the controller named 'Metadata'."}
Do I need to create a controller/action for serving the metadata? If so, how is that action implemented?
Turns out it had to do with my replacement of the IAssembliesResolver.
I had implemented a custom version to provide only the component assemblies that I had implemented controllers in. However, as the error said, it couldn't find a controller named MetadataController. Turns out, OData implements one: System.Web.OData.MetadataController, which provides for the $metadata keyword.
Since I had implemented my own IAssembliesResolver, the System.Web.OData assembly wasn't being included, and $metadata failed. Once I discovered this, and updated my assembly resolver to explicitly include the OData assembly, it now works as it should.

ASP.NET Odata Web API CRUD Operations Not Working

I have spent a about 2 days setting up my Odata Web API project that was before a simple Asp.net mvc4 project.
But still I am not successful in operating even CRUD Operations.
It says this:
<m:message xml:lang="en-US">
No HTTP resource was found that matches the request URI 'http://localhost:53208/odata/Product'.
</m:message>
<m:innererror>
<m:message>
No action was found on the controller 'Product' that matches the request.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
It means its reaching the Controller but not finding actions or there is some problem in my routing settings.
I have following in my WebApiConfig:
config.Routes.MapODataRoute("OData","odata",GetEdmModel());
My getEdmModel method:
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Product");
builder.EntitySet<OfferedProduct>("OfferedProduct");
IEdmModel model = builder.GetEdmModel();
return model;
}
My controller is like this:
public class ProductController : EntitySetController<Product,int>
{
private OfferAssistantDbContext db = new OfferAssistantDbContext();
List<Product> Products = new OfferAssistantDbContext().Products.ToList();
// GET api/Product
[Queryable(PageSize = 10)]
public override IQueryable<Product> Get()
{
return Products.AsQueryable();
}
// GET api/Product/5
public Product GetProduct([FromODataUri] int id)
{
return Products[id];
}
/// and so on... but for this time lets work only on GET operation
Now when I write this in my browser:
http://localhost:53208/odata/Product
it says the error I showed above..
Please guide me where is the problem?
I believe that your controller needs to inherit from ODataController:
public class ProductController : ODataController

Categories