OData controller method with multiple parameters - c#

I need to create an OData controller with this assign.
http://xxxxxx/odata/mock/Itens(NumContrato='1234',IdItem='10')/Pedidos
ItensController
public class ItensController : ODataController
{
[HttpPost]
[ODataRoute("(NumContrato={NumContrato},IdItem={IdItem})/Pedidos")]
public IQueryable<Pedido> Pedidos([FromODataUri] string NumContrato, [FromODataUri] string IdItem)
{
... do something
}
}
WebApiConfig.cs
...
config.Routes.MapODataServiceRoute(
"ODataRoute",
"odata/mock",
model: GetModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
...
public static IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
...
builder.EntitySet<Dominio.ODataSapFake.Item>("Itens");
builder.EntitySet<Dominio.ODataSapFake.LinhaDeServico>("LinhasDeServicos");
builder.EntitySet<Dominio.ODataSapFake.Pedido>("Pedidos");
var a = builder.Entity<Dominio.ODataSapFake.Item>().Collection.Action("Pedidos");
a.Parameter<string>("NumContrato");
a.Parameter<string>("IdItem");
a.ReturnsCollectionFromEntitySet<Dominio.ODataSapFake.Pedido>("Pedidos");
...
return builder.GetEdmModel();
}
An error occurs when call service.
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code/>
<m:message xml:lang="en-US">
No HTTP resource was found that matches the request URI 'http://localhost:4492/odata/mock/Itens(NumContrato='100',IdItem='00040')/Pedidos'.
</m:message>
<m:innererror>
<m:message>No routing convention was found to select an action for the OData path with template '~/entityset/key/navigation'.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
</m:error>

Either your URL is incorrect, or your configuration is incorrect, lets explore both possibilities:
Assuming that the config is correct, you have declared Action called Pedidos has 2 parameters, and it is bound to the collection, no to an item... the URL that matches your definition is actually:
~/Itens/Pedidos(NumContrato='1234',IdItem='10')
Assuming that URL is correct, then you need to configure the endpoint as an Item bound Action and we also assume that the Itens record already has the keys NumContrato and IdItem defined:
builder.EntitySet<Dominio.ODataSapFake.LinhaDeServico>("LinhasDeServicos");
builder.EntitySet<Dominio.ODataSapFake.Pedido>("Pedidos");
var itens = builder.EntitySet<Dominio.ODataSapFake.Item>("Itens");
// Key should be defined by attribute notation, but we can redefine it here for clarity
itens.EntityType.HasKey(x => x.NumContrato);
itens.EntityType.HasKey(x => x.IdItem);
// declare the Action bound to an Item
itens.Item.Action("Pedidos")
.ReturnsCollectionFromEntitySet<Dominio.ODataSapFake.Pedido>("Pedidos");
Update 1 - GET support:
Action supports HTTP Post, which OP has decorated the method with, but if you intend to support HTTP GET, then you must configure the endpoint as a Function instead. The syntax is still the same.
don't forget to remove the [HttpPost] attribute, consider replacing it with [EnableQuery] or [HttpGet]
Update 2 - Item Navigation with Composite Key
If the Item data record has a Navigation Property called Pedidos and your intention is to support the standard Item navigation down this property path then you do not need to include this information in the config all.
remove the [HttpPost], this is a GET request.
remove the [ODataRoute] attribute, the default routes should work
Make sure that you use the names of the key properties, using the same casing and order as they are declared with in the configuration.
If you are using auto configuration, then the order will match the Key attribute column specification in your data model.
For OData v3 you may need to follow this advice: Odata v3 Web Api navigation with composite key

Related

What is the Name property on Http verbs attribute?

In the docs for this property, nothing is said, so what exactly does this property do?
Reference Routing to controller actions in ASP.NET Core :
Route Name
The following code defines a route name of Products_List:
public class ProductsApiController : Controller
{
[HttpGet("/products/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id) { ... }
}
Route names can be used to generate a URL based on a specific route. Route names have no impact on the URL matching behavior of routing and are only used for URL generation. Route names must be unique application-wide.
Reference Generating URLs by route

OData v4 Custom Action for File Upload

I have an OData controller with standard verbs for CRUD. Everything is working fine. Now I need to add a custom action to perform file upload. I try to add a method to my existing controller like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
//handle uploaded content logic here...
}
But when I try to invoke it by doing a POST:
http://localhost/UploadFile
I get this error:
System.InvalidOperationException: No non-OData HTTP route registered.
What should I do for this custom action to allow file upload?
You need to declare the Action as part of the EdmModel, in the following example I am assuming that your Entity Type is Attachment, and your controller class name is AttachmentsController. By convention, your EntitySet name must then be Attachments
var attachments = builder.EntitySet<Attachment>("Attachments");
attachments.Action(nameof(AttachmentsController.UploadFile))
.Returns<System.Net.Http.HttpResponseMessage>();
The important part of the above statement is the return type, if you do not declare the return type correctly in your EdmModel then you will find your endpoints returning 406 errors - Unacceptable, even though your method executes correctly, which is really confusing the first time you run into it. This is because OData will still try to parse your response to match the Accept header from the request before completing the response.
Try to use 'nameof' when mapping functions and actions instead of 'magic strings' or constants so that the compiler can pickup basic issues like wrongly defined route.
With this approach you do not need the Route attribute on the method header and the action will be included in the metadata document and therefore swagger outputs.

WebAPI 2 : Default GET ALL is invoked with wrong parameter

I am using WebAPI 2 with EF and scaffolding webapi controllers from visual studio.
Each controller is created with 4 default verbs (GET,PUT,DELETE,POST) and 5 actions. while there are two versions of GET action.
IQueryable<entity> GetEntities ()
Task<IHttpActionResult> GetEntity(GUID key) // default is int id but I changed to guid.
I am using attribute routing and route prefix for the controller. just some fancy keywords for better management of url. [RoutePrefix("api/v3/Company")]
Problem :
Ideally when a wrong parameter is sent in url, it should return error, but it is not raising error, instead it fall back to the action without parameter.while if I send a wrong GUID, it shows error.
Like if I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
it shows the right result.
when I call :
http://localhost:8080/api/v3/Company/1f7dc74f-af14-428d-aa31-147628e96500 (wrong key)
it set back to GetEntity() function and shows all records
when I call:
http://localhost:8080/api/v3/Company/1 (not a GUID length parameter)
it do the same and shows all records.
I am using attribute [Route("{id:guid}")]
Really appreciate if I can get some guidance on this!
It is most likely that the route is defaulting back to the convention-based mapping.
You need to explicitly make apply the route attribute on actions to let the routing know that it is the default route got GET
[RoutePrefix("api/v3/Company")]
public class CompanyController : ApiController {
//GET api/v3/Company
[HttpGet]
[Route("")] //Default Get
public IQueryable GetEntities() { ... }
//GET api/v3/Company/1f7dc74f-af14-428d-aa31-147628e965b2
[HttpGet]
[Route("{id:guid}")] // ALSO NOTE THAT THE PARAMETER NAMES HAVE TO MATCH
public Task<IHttpActionResult> GetEntity(Guid id) { ... }
//...other code removed for brevity
}
Make sure that attribute routing is enabled in the web api config
config.MapHttpAttributeRoutes();

Changing default routes in WebAPI

Being new and transitioning from ruby on rails. I would like to understand how to change the default routes to use custom routing.
For eg the current routes in my WebApiConfig.cs is as follows:
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional}
So the URL GET api that I have configured in my controller is:
https://localhost:44300/api/Controller?type=FirstType
The corresponding method defined in my controller is:
public HttpResponseMessage GetControllerByType(string type)
What are the changes to be made to execute the same as above when calling the WEB api using the URL:
https://localhost:44300/api/Controller/FirstType
Assuming that you're using WebAPI 2 you can create custom routes using attributes on your actions within the controllers.
Add the following to the Register method of your WebApiConfig.cs:
config.MapHttpAttributeRoutes();
The above should be added BEFORE the default routing configuration that you already posted above.
Then within your controllers you can use annotations to describe routes:
[Route("controller/{type}")]
[HttpGet]
public HttpResponseMessage GetControllerByType(string type)
Values that are inside the brackets represent variables that are bound to the parameters of the method via their names.
You can also annotate classes themselves to have prefixes that apply to all methods within that controller:
[RoutePrefix("stuff")]
public class MyController : ApiController
{
[Route("myAction/{id}")] //route to this is via /stuff/myAction/{id}
[HttpGet]
public HttpResponseMessage MyMethod(string id)
{ ... }
}
The only 'gotcha' is that query strings should NOT be within the route template, those are represented by optional parameters (those with default values) in your method signature, which don't also have an optional annotation within the route template string. (ie: "myAction/{id:string?}" must have a default value in the signature, but all other parameters that aren't id with default values can be assigned via a query string)
Anything that you do not provide a route template for using an annotation will fallback to using the default routes specified in the WebApiConfig, which is by default "/api/controller_name/action_name/"
Here's a great tutorial on all the custom routing using attributes that you can setup: Attribute Routing in ASP.NET WebAPI 2

How to change the way REST Web API is called for ASP.NET

My controller currently looks like:
[Jsonp filter]
public class ProductController : Controller
{
public Json GetProduct(string id)
{
Product x;
//code
return Json(x, JsonRequestBehavior.AllowGet);
}
}
I am able to get a product doing this:
api/product/getproduct/5
But, I want to be able to access it like this:
api/product/5
What change do I need to make to do this?
EDIT: I am actually using Jsonp because I need to call this API from a different domain and get a json object back. Would this be possible using ApiController? Otherwise is there a way to do this without switching to ApiController?
You will have to edit your webapiconfig (located in the App_Start folder).
You will need to add something like this before any other route (to make sure it is caught first):
// Map Http Route takes in 3 parameters below
// param 1 is the name of the route.. This has nothing to do with class names or method names
// param 2 is the route itself. Route parameters are denoted in curly braces {likethis}
// param 3 sets up defaults
config.Routes.MapHttpRoute("GetProductApi", "api/product/{id}",
new {
controller = "Product", // the name of the controller class (without the Controller suffix)
action = "GetProduct", // the name of your method
id = RouteParameter.Optional
});
Also, your code for your controller looks like it isn't an API controller. Regardless, this is a routing problem. You can add a route configuration in your regular route config if you 100% need to.
Your code above is not a WebApi Controller it is an MVC Controller. Your class needs to inherits from ApiController instead like:
public class ProductController : ApiController{
...
Regarding your method I am not sure why you used Json as returned type since it is part of the MediaFormatter configuration to define the returned format, it should not be defined at method/function level.
It looks like the correct method declaration will be something like:
public Product GetProduct(string id)
{
Product x; //probably you want initialize it like new Product();
return x;
}
Update JsonP
WebApi works based on MediaFormatters as explained earlier. In order to use JsonP you need to use the proper media formatter there are several out there but how about:
http://www.nuget.org/packages/WebApi.JsonP
If you wish to read more about JsonP formatters for WebApi here is a SO Post about this:
.net 4.5 ASP.Net web API JSONP support
Jsonp in WebApi
Your ProductController should derive from ApiController, instead of Controller.
Building on #Dalorzo's answer, if/when you can convert to an APIController, and if you can use WebAPI 2, you can use decorator attributes on your methods that will alter the routes and even the HTTP verbs to use for the method... which is really nice because everything you need to know about that API call is right there at the function signature. It's quite robust and intuitive, and I highly recommend it.

Categories

Resources