ASP.Net Web Api routing issue with ActionName - asp.net

I am working with an ASP.Net Web Api project on Web Developer Express 2010. The routing config is defined in WebApiConfig.cs as:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = RouteParameter.Optional,
id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi4",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = RouteParameter.Optional }
);
}
An API Controller called "GCURObservationController" has an action as:
[HttpGet, ActionName("retrieveCuringMaps")]
public IList<SimpleCuringMapsModel> retrieveCuringMaps()
{
... ...
return jsonCuringMapModels;
}
The project was compiled and run successfully. However, I had to go to
http://localhost:2061/api/GCURObservation/retrieveCuringMaps/0
to get the action triggered (action name followed by any integer), rather than what I expected to be
http://localhost:2061/api/GCURObservation/retrieveCuringMaps
That means an arbitrary integer had to follow the action name to get it right. Otherwise, the error was returned. I don't want this action to be triggered with any param.
{"Message":"The request is invalid."}
How to get the second URL to work? Thanks
Cheers,
Alex

If you are using Web API 2, following is one solution you could use. In the below example, I am using attribute routing and conventional routing together in one controller. Here all the actions except GetCustomerOrders are reached via conventional route "DefaultApi".
In general the idea here is not new, that is...even without Web API 2's attribute routing, you could define routes for each individual action of a controller in the global route table, but attribute routing makes this process easier as you can define routes directly and near to the action.
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
[RoutePrefix("api/customers")]
public class CustomersController : ApiController
{
public IEnumerable<Customer> GetAll()
{
}
public Customer GetSingle(int id)
{
}
public void Post(Customer customer)
{
}
public void Put(int id, Customer updatedCustomer)
{
}
public void Delete(int id)
{
}
[Route("{id}/orders")]
public IEnumerable<Order> GetCustomerOrders(int id)
{
}
}

Related

How to define WebAPI route in asp.net

Background
I have a controller
public class WorkOrderController : ApiController
{
// GET: api/WorkOrder
public IEnumerable<WhateverObj> Get()
{
//etc..
}
// GET: api/WorkOrder/123
public WhateverObj Get(string id)
{
//etc..
}
// GET: api/WorkOrder/5/020
public WhateverObj Get(string id, string opID)
{
//etc...
}
}
and the following routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServicesApi",
routeTemplate: "api/{controller}/{id}/{opID}",
defaults: new { opID = RouteParameter.Optional }
);
This works as expected, I can navigate to the above example URLs.
The Problem
Now i want to create another Controller with only 1 method as follows:
public class FilteredWorkOrderController : ApiController
{
//By WorkCentreID = ABC, XYZ, UVW
public IEnumerable<WhateverObj> Get(string workCentreID)
{
//etc...
}
}
The following URL hits the above method ok.
http://localhost:62793/api/FilteredWorkOrder/?workCentreID=ABC
But the (alternative) form
http://localhost:62793/api/FilteredWorkOrder/ABC
does not work, error message is:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:62793/api/FilteredWorkOrder/ABC'.","MessageDetail":"No action was found on the controller 'FilteredWorkOrder' that matches the request."}
What route mapping configuration do I need, to get the alternative URI to also work?
I tried
config.Routes.MapHttpRoute(
name: "FilteredApi",
routeTemplate: "api/{controller}/{workCentreID}"
);
but this does NOT work.
I've noticed that in the Filtered controller, if I change my parameter name in Get(string workCenterID) to Get(string id), then both URLs work!
http://localhost:62793/api/FilteredWorkOrder/?id=ABC
http://localhost:62793/api/FilteredWorkOrder/ABC
What is so magical about the parameter name: 'id'?
I want my parameter to be called workCentreID.
1) Parameter names in route template should match your action's arguments names. This is a convention. So, when you have registered the default route in config:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServicesApi",
routeTemplate: "api/{controller}/{id}/{opID}",
defaults: new { opID = RouteParameter.Optional }
);
and try to request http://localhost:62793/api/FilteredWorkOrder/ABC - application can't find an action, because there are no pattern matches.
2) The order, in which routes are registered in your config, matters. If you have more than one potential pattern matches, the engine will choose the first one. After changes, you are describing in second case, the engine looks in routetable and matches again the default route for url http://localhost:62793/api/FilteredWorkOrder/ABC - which doesn't correlate with action signature.
3) For second case - if you will place your custom route registration before default route - your URL should work:
config.Routes.MapHttpRoute(
name: "FilteredApi",
routeTemplate: "api/{controller}/{workCentreID}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Besides, take a look at this article, describing routing in WEB API
The comments / answers pointed me in the right direction to use attribute-based routing. What i needed was
public class FilteredWorkOrderController : ApiController
{
//By WorkCentreID = ABC, XYZ, UVW
[Route("api/WorkOrders/{workCentreID}")]
public IEnumerable<WhateverObj> Get(string workCentreID)
{
//etc...
}
}
and i can make a request with http://localhost:62793/api/WorkOrders/ABC
However, it appears with attribute-based routing, the alternative form does not work, that is, i CANNOT make a request using:
http://localhost:62793/api/WorkOrders/?workCentreID=ABC
I'd suggest you use route attributes. Why?
It never brings you any troubles like patterns you described.
It's really more readable than patterns.
[Route("{workCentreID}")]
public IEnumerable<WhateverObj> Get(string workCentreID)
{
//etc...
}

how to pass parameter name "action" in asp.net web api

Can not send parameter name "action" by url in asp.net web api 2.0.
Example:
http://host:port/controller_name?action=2&...
if you do so:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{action}",
defaults: new
{
action=RouteParameter.Optional
}
);
method in controller:
public HttpResponseMessage Get(int action)
{
return ResponseXml();
}
gives an error message:
in the dictionary path name of the parameter "action" is contained in
the URL-address more than once
How to pass parameter name "action" as a parameter, rather than the action method ?
thanks
Since the name action is included in the querystring part(?action=2), no need to change the route map. The framework will bind the value to the action paramter in the action method. Remove the extra {action} in routeTemplate. And since your url format doesn't contain {action} host:port/controller_name?action=2&login=, remove {action} from routemap.
So, your route map will be
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new
{
id=RouteParameter.Optional
}
);
You could try using Attribute Routing and include it in the route template.
//GET [controller_route]/2
[HttpGet]
[Route("{action:int}")]
public HttpResponseMessage Get(int action)
{
return ResponseXml();
}
which will let you use the following url
http://host:port/controller_name/2
where action parameter will be mapped to 2.
Remember to enable the attribute routing during configuration.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}

Disambiguation in ASP.NET WebAPI routing using Action

Hi I have this REST services
GET admin/delegateduser
GET admin/delegateduser/id
GET delegateduser
I succeed configuring the route with this mapping:
config.Routes.MapHttpRoute(
name: "ActionApiWithId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And this controllers
public class AdminController : ApiController
{
// GET api/admin/delegatedusers
[ActionName("delegatedusers")]
public IEnumerable<x> Get()
{
}
// GET api/delegatedusers/<userid>
[ActionName("delegatedusers")]
public x Get(String id)
{
}
}
public class DelegatedUsersController : ApiController
{
public x Get()
{
}
}
The problem is that I add a new method that is not properly resolved. The method is
DELETE enrollrequest/id
Using this mapping and controller
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
public class EnrollRequestController : ApiController
{
/// DELETE api/enrollrequest/<id>
public void Delete(String id)
{
}
}
If I put the DefaultApi routMapping on top of the WebApiConfig file then this new method is resolved but GET admin/delegateduser is not. So it looks like this two methods conflict on URL resolutions.
How would be the right way to map the methods? May be everything should be more simple and I got too complicated....
Any help is wellcomed.
Thanks in advance.
Finally I found the solution,
The conflict was that DELETE enrollrequest/ and GET admin/delegateduser both fit with the rules
{controller}/id
{controller}/action
I solve that adding a constraint this way:
config.Routes.MapHttpRoute(
name: "ActionApiWithId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { action = "delegatedusers" }
);
Now the call DELETE enrollrequest/ does not fit the route because the don't fullfill the constraint.

Url rewriting/routing and webapi

I am designing a theme based website using webapi. I have saved my themes in folders A, B and C. The themes are actual html pages that fetch some content( like photos) etc. from database depending upon the client.
So my actual pages are like: localhost/A/index.html?client=Client1 (where A is the theme name).
I want to show the url to user as localhost/client1/index.html. I can fetch the theme name from client's id ( client1 in the example).
What combination of routing and url rewriting should I use to accomplish this?
I am hosting in IIS? And how should I do that?
You could have the following custom route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "CustomRoute",
routeTemplate: "api/{client}/index.html",
defaults: new { controller = "values" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and the corresponding API controller:
public class ValuesController : ApiController
{
// GET /api/{client}/index.html
public HttpResponseMessage Get(string client)
{
// The client variable will represent the value
// that was passed in the route segment
return Request.CreateResponse(HttpStatusCode.OK, client);
}
}

Web API Routing - api/{controller}/{action}/{id} "dysfunctions" api/{controller}/{id}

I have the default Route in Global.asax:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
I wanted to be able to target a specific function, so I created another route:
RouteTable.Routes.MapHttpRoute(
name: "WithActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
So, in my controller, I have:
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
Calling .../api/records/bycategoryid/5 will give me what I want.
However, calling .../api/records/1 will give me the error
Multiple actions were found that match the request: ...
I understand why that is - the routes just define what URLs are valid, but when it comes to function matching, both Get(int id) and ByCategoryId(int id) match api/{controller}/{id}, which is what confuses the framework.
What do I need to do to get the default API route to work again, and keep the one with {action}? I thought of creating a different controller named RecordByCategoryIdController to match the default API route, for which I would request .../api/recordbycategoryid/5. However, I find that to be a "dirty" (thus unsatisfactory) solution. I've looked for answers on this and no tutorial out there on using a route with {action} even mentions this issue.
The route engine uses the same sequence as you add rules into it. Once it gets the first matched rule, it will stop checking other rules and take this to search for controller and action.
So, you should:
Put your specific rules ahead of your general rules(like default), which means use RouteTable.Routes.MapHttpRoute to map "WithActionApi" first, then "DefaultApi".
Remove the defaults: new { id = System.Web.Http.RouteParameter.Optional } parameter of your "WithActionApi" rule because once id is optional, url like "/api/{part1}/{part2}" will never goes into "DefaultApi".
Add an named action to your "DefaultApi" to tell the route engine which action to enter. Otherwise once you have more than one actions in your controller, the engine won't know which one to use and throws "Multiple actions were found that match the request: ...". Then to make it matches your Get method, use an ActionNameAttribute.
So your route should like this:
// Map this rule first
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
RouteTable.Routes.MapRoute(
"DefaultApi",
"api/{controller}/{id}",
new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);
And your controller:
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
You can solve your problem with help of Attribute routing
Controller
[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }
URI in jquery
api/category/1
Route Configuration
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
and your default routing is working as default convention-based routing
Controller
public string Get(int id)
{
return "object of id id";
}
URI in Jquery
/api/records/1
Route Configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Review article for more information Attribute routing and onvention-based routing here & this
Try this.
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var json = config.Formatters.JsonFormatter;
json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional , Action =RouteParameter.Optional }
);
}
}
The possible reason can also be that you have not inherited Controller from ApiController.
Happened with me took a while to understand the same.
To differentiate the routes, try adding a constraint that id must be numeric:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
constraints: new { id = #"\d+" }, // Only matches if "id" is one or more digits.
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);

Resources