URL parameters in MVC 4 Web API - c#

Let say I have two methods in MVC 4 Web API controller:
public IQueryable<A> Get() {}
And
public A Get(int id) {}
And the following route:
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This works as expected. Adding a one more parameter, e.g.:
public IQueryable<A> Get(int p) {}
public A Get(int id, int p) {}
leads to the situation when MVC returns 404 for the following request:
GET /controller?p=100
Or
GET /controller/1?p=100
with message "No action was found on the controller 'controller' that matches the request"
I expect that URL parameters should be wired by MVC without issues, but it is not true. Is this a bug or my misunderstanding of how MVC maps request to action?

If you think about what you're attempting to do and the routes you're trying, you'll realize that the second parameter "p" in your case, needs to be marked as an optional parameter as well.
that is your route should be defined like so:
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}/{p}",
defaults: new { id = RouteParameter.Optional, p = RouteParameter.Optional });
Once you do this, the URL
/controller?p=100
will map to your
public IQueryable<A> Get(int p) {}
method and a URL like so:
/controller/1?p=100
will map to your
public A Get(int id, int p) {}
method, as you expect.
So to answer your questions....no this is not a bug but as designed/expected.

In the WebApiConfig add new defaults to the httproute
RouteParameter.Optional for the additional routes did not work for me
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{voucher}",
defaults: new { id = RouteParameter.Optional ,defaultroute1="",defaultroute2=""}
);

Related

Routing issue with overloaded Get method in WebAPI

I have 2 Get methods in Product controller as below:
public IHttpActionResult Get(string keywordSearch)
[Route("api/Product/{id:long}")]
public IHttpActionResult Get(long id)
The following url works:
http://localhost:61577/api/Product?keywordSearch=test
http://localhost:61577/api/Product/1
This one fails, with message:
http://localhost:61577/api/Product/test
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:61577/api/Product/test'.",
"MessageDetail": "No action was found on the controller 'Product' that matches the request."
}
The WebApiConfig.cs has following configurations:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Please advice, what changes should I make in API function or Config to make that work.
You would need to specify the routes in your routes configuration for both the action methods something like:
// for number type parameter only
config.Routes.MapHttpRoute(
name: "IdSearch",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
and then register another one for string parameter :
// for string parameter overload action route
config.Routes.MapHttpRoute(
name: "KeyWordSearch",
routeTemplate: "api/{controller}/{keywordSearch}",
defaults: null
);
and in your action declaration apply propery attribute values on string parameter overload one, so that both will look like:
[Route("api/Product/{keywordSearch}")]
public IHttpActionResult Get(string keywordSearch)
[Route("api/Product/{id:long}")]
public IHttpActionResult Get(long id)
Another way is that you can use the RoutePrefix on your Controller class and then the Route attribute on your action methods, so that you don't have to duplicate the Prefix of Route on each action method:
[RoutePrefix("api/product")]
public class ProductController : ApiController
{
[Route("{keywordSearch}")]
public IHttpActionResult Get(string keywordSearch)
[Route("{id:long}")]
public IHttpActionResult Get(long id)
}
This should keep you going.
Hope it helps!

Web API Multiple Actions were found that match this request, if called without parameter

I've got a problem using ASP.NET WebAPI Controller. What i try to build is a controller which contains two actions that can be called by a http-get. What i have so far:
[HttpGet]
public IList<Model> Get()
{
...
}
[HttpGet]
public IList<Model> GetAllWorkshops()
{
...
}
I also edited my RouteConfig.cs to the following:
routes.MapHttpRoute(
name: "API Default With ID",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}"
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =UrlParameter.Optional }
);
The strange behaviour is that if i call : http://localhost:1536/Api/Workshop/Get or localhost:1536/Api/Workshop/GetAllWorkshops, i get the exception that multiple actions were found that match this request. But if i call localhost:1536/Api/Workshop/Get/1 or localhost:1536/Api/Workshop/GetAllWorkshops/1 it was works perfectly. So i think that the route with the id works. But why do the other two requests throw these exceptions?
I tried almost every imaginable route. It would be great if you find my mistakes!
Thank you very much!
Because route named "API Default With ID" has id as optional, so /api/workshop/get matched both the first and second route, hence the error.
To fix this error, you can remove second route because it is just a subset of the first route template.
However, it is better to use new Attribute Routing introduced in Web API 2, it is by far more maintainable and easy to understand than using old-style mapping.
If you use attribute mapping, your class will look like:
[RoutePrefix("api/workshop")]
public class WorkshopController : ApiController {
[HttpGet]
[Route("Get")]
public IList<Model> Get()
{
...
}
[HttpGet]
[Route("GetAllWorkshops")]
public IList<Model> GetAllWorkshops()
{
...
}
}
Very clear and concise.

Web API route being ignored

I have these two routes defined:
routes.MapRoute(
name: "GetVoucherTypesForPartner",
url: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner"}
);
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional}
);
In my PartnerProfile controller, I have 2 methods:
public Partner Get(string id)
{ }
public IEnumerable<string> GetVoucherTypesForPartner(string id)
{ }
If I hit the url ~/api/Partner/1234 then, as expected, the Get method is called.
However, if I hit the url ~/api/Partner/1234/VoucherType then the same Get method is called. I am expecting my GetVoucherTypesForPartner to be called instead.
I'm pretty sure something in my route setup is wrong...
You seem to have mapped standard MVC routes, not Web API routes. There's a big difference. The standard routes are used by controllers deriving from the Controller class, but if you are using the ASP.NET Web API and your controllers are deriving from the ApiController type then you should define HTTP routes.
You should do that in your ~/App_Start/WebApiConfig.cs and not inside your ~/App_Start/RouteConfig.cs.
So go ahead:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "GetVoucherTypesForPartner",
routeTemplate: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
and then:
public class PartnerController : ApiController
{
public Partner Get(string id)
{
...
}
public IEnumerable<string> GetVoucherTypesForPartner(string partnerId)
{
...
}
}
Things to notice:
We have defined HTTP routes not standard MVC routes
The parameter that the GetVoucherTypesForPartner action takes must be called partnerId instead of id in order to respect your route definition and avoid any confusions

Multiple actions were found that match the request Web API?

I am using web API and i am new in this. I am stuck in a routing problem. I have a controller with following actions :
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies()
{
return db.Ceremonies.AsEnumerable();
}
// GET api/Ceremony/5
public Ceremony GetCeremony(int id)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
{
return filter.Ceremonies();
}
The problem occure when i added the action GetFilteredCeremonies to my controller. After adding this when i make an ajax call to GetCeremonies action then it return an Exception with following message :
"Message":"An error has occurred.","ExceptionMessage":"Multiple actions were
found that match the request
FYI: The parameter Search is the Model class which contains properties and a function name Ceremonies.
EDIT
Route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you're not requirement bound to use REST services that use api/{controller}/{id} route and attempt to resolve action based on method GET/POST/DELETE/PUT, you can modify your route to classic MVC route to api/{controller}/{action}/{id} and it will solve your problems.
The problem here is your 2 Get methods will resolve to api/Ceremony and MVC does not allow parameter overloading. A quick workaround (not necessarily the preferred approach) for this sort of problem is to make your id parameter nullable e.g.
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies(int? id)
{
if (id.HasValue)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
else
{
return db.Ceremonies.AsEnumerable();
}
}
However, you would then be returning a list of ceremonies when with 1 item when your trying to query for a single ceremony - if you could live with that then it may be the solution for you.
The recommended solution is to map your paths appropriately to the correct actions e.g.
context.Routes.MapHttpRoute(
name: "GetAllCeremonies",
routeTemplate: "api/{controller}",
defaults: new { action = "GetCeremonies" }
);
context.Routes.MapHttpRoute(
name: "GetSingleCeremony",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetCeremony", id = UrlParameter.Optional }
);
Luckily nowadays with WEB API2 you can use Attribute Routing. Microsoft has gone open source on a big scale and then a wizard named Tim McCall contributed it from the community. So since somewhere end 2013, early 2014 you can add attributes like [Route("myroute")] on your WEB API methods. See below code example.
Still - as I just found out - you have to make sure to use System.Web.Http.Route and NOT System.Web.Mvc.Route. Otherwise you'll still get the error message Multiple actions were found that match the request.
using System.Web.Http;
...
[Route("getceremonies")]
[HttpGet]
// GET api/Ceremony
public IEnumerable<Ceremony> GetCeremonies()
{
return db.Ceremonies.AsEnumerable();
}
[Route("getceremony")]
[HttpGet]
// GET api/Ceremony/5
public Ceremony GetCeremony(int id)
{
Ceremony ceremony = db.Ceremonies.Find(id);
return ceremony;
}
[Route("getfilteredceremonies")]
[HttpGet]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
{
return filter.Ceremonies();
}
Here is my controller:
public class PhoneFeaturesController : ApiController
{
public List<PhoneFeature> GetbyPhoneId(int id)
{
var repository = new PhoneFeatureRepository();
return repository.GetFeaturesByPhoneId(id);
}
public PhoneFeature GetByFeatureId(int id)
{
var repository = new PhoneFeatureRepository();
return repository.GetFeaturesById(id);
}
}
Here is my api routing:
config.Routes.MapHttpRoute(
name: "ApiWithId",
routeTemplate: "Api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^[0-9]+$" });
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: null,
constraints: new { name = #"^[a-z]+$" });
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "Get" },
constraints: new { id = #"^[0-9]+$" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I tested it like this:
/api/PhoneFeatures//api/PhoneFeatures/GetFeatureById/101
/api/PhoneFeatures/GetByFeatureId/12
It works smooth in every condition :)
I found another fix that doesn't require moving methods out of the controller or changing the route mapping config. Just add the [NonAction] attribute to the method you want to exclude:
[NonAction]
public IEnumerable<Ceremony> GetFilteredCeremonies(Search filter)
Please check you have two methods which has the different name and same parameters.
If so please delete any of the method and try.
This error was raised because there are two methods which are looking for same parameters. try to delete any one of them and try...
I hope you are doing HttpGet while you invoke GetFilteredCeremonies(Search filter)
In that case, you cannot pass complex object in GET request like Search that you are passing.
If for some reason, you definitely want to get complex types in your get request, there are some work around. You may need to write a custom model binder and then set the attribute. please refer this article.
Edit Your WebApiConfig.cs in App_Start folder on the root of project and add {action} to routeTemplate parameter in MapHttpRoute Method like below :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Multiple actions for the same HttpVerb

I have a Web API controller with the following actions:
[HttpPut]
public string Put(int id, JObject data)
[HttpPut, ActionName("Lock")]
public bool Lock(int id)
[HttpPut, ActionName("Unlock")]
public bool Unlock(int id)
And the following routes mapped:
routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "ApiAction",
routeTemplate: "api/{controller}/{action}/{id}"
);
When I make the following requests everything works as expected:
PUT /api/items/Lock/5
PUT /api/items/Unlock/5
But when I attempt to make a request to:
PUT /api/items/5
I get the following exception:
Multiple actions were found that match the request:
Put(int id, JObject data)
Lock(int id)
Unlock(int id)
I tried adding an empty action name to the default route but that did not help:
[HttpPut, ActionName("")]
public string Put(int id, JObject data)
Any ideas how I can combine default RESTful routing with custom action names?
EDIT: The routing mechanism is not confused by the choice of controller. It is confused by the choice of action on a single controller. What I need is to match the default action when no action is specified. Hope that clarifies things.
This is an expected error from the default action selector which is the ApiControllerActionSelector. You basically have three action methods which correspond to HTTP Put verb. Also keep in mind that the default action selector considers simple action parameter types which are all primitive .NET types, well-known simple types (System.String, System.DateTime, System.Decimal, System.Guid, System.DateTimeOffset, System.TimeSpan) and underlying simple types (e.g: Nullable<System.Int32>).
As a solution to your problem I would create two controllers for those as below:
public class FooController : ApiController {
public string Put(int id, JObject data)
}
public class FooRPCController : ApiController {
[HttpPut]
public bool Lock(int id)
[HttpPut]
public bool Unlock(int id)
}
the routes would look like as below:
routes.MapHttpRoute(
name: "ApiAction",
routeTemplate: "api/Foo/{action}/{id}",
defaults: new { controller = "FooRPC" }
);
routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/Foo/{id}",
defaults: new { id = RouteParameter.Optional, controller = "Foo" }
);
On the other hand (not completely related to your topic), I have three blog posts on action selection, especially with complex type parameters. I encourage you to check them out as they may give you a few more perspective:
Complex Type Action Parameters and Controller Action Selection with ASP.NET Web API
Complex Type Action Parameters with ComplexTypeAwareActionSelector in ASP.NET Web API - Part 1
Complex Type Action Parameters with ComplexTypeAwareActionSelector in ASP.NET Web API - Part 2
With the help of Giscard Biamby, I found this answer which pointed me in the right direction. Eventually, to solve this specific problem, I did it this way:
routes.MapHttpRoute(
name: "ApiPut",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "Put" },
constraints: new { httpMethod = new HttpMethodConstraint("Put") }
);
Thanks #GiscardBiamby
Firstly, remove [HttpPut, ActionName("")] and then modify your route to this
config.Routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^[0-9]+$" }
);

Categories