Multiple actions were found that match the request in webapi - c#

I have controller with various actions and one of them is:
[HttpGet]
public IList<string> GetFoo(string id = null)
{ ... }
This action has individual route:
routes.MapHttpRoute(
name: "GetFoos",
routeTemplate: "api/my/foo/{_id}",
defaults: new { controller = "My", action = "GetFoo" }
);
When I add another action:
[HttpGet]
public IList<string> GetBar()
{ ... }
Request to localhost/api/my/foo/ fails:
Multiple actions were found that match the request:
↵System.Collections.Generic.IList`1[System.String] GetFoo(System.String) on type Controllers.MyController
↵System.Collections.Generic.IList`1[System.String] GetBar() on type Controllers.MyController"
Can someone explain please why this happens? I specified action = "GetFoo" for api/my/foo why it matches GetBar?

It could be that you configure route as following, and request without ID - /api/my/foo.
config.Routes.MapHttpRoute(
name: "GetFoos",
routeTemplate: "api/my/foo/{id}",
defaults: new {controller = "My", action = "GetFoo"}
);
// Default
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
If so, first route doesn't match and fall throws to default route, but default route matches with multiple actions.
Note: GetFoos route will work if you request with ID explicitly - /api/my/foo/1
Ideally, if you see yourself using too many custom routes, you might want to consider using Route Attribute available in Web API 2, instead of creating individual route in Route config.
For example,
[RoutePrefix("Api/My")]
public class MyController : ApiController
{
[HttpGet]
[Route("foo/{id:int}")]
public IList<string> GetFoo(int id)
{
return new string[] {"Foo1-" + id, "Foo1-" + id};
}
[HttpGet]
[Route("bar/{id:int}")]
public IList<string> GetBar(int id)
{
return new string[] {"Bar1-" + id, "Bar1-" + id};
}
}

Related

How to add custom methods to ASP.NET WebAPI controller?

In ASP.NET MVC WebAPI project by default we have created following controller
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
But is possible to add here any custom methods so they can support get/post as well?
Thank you!
You can use attributes such as the RoutePrefix with the Http type.
[Route("ChangePassword")]
[HttpPost] // There are HttpGet, HttpPost, HttpPut, HttpDelete.
public async Task<IHttpActionResult> ChangePassword(ChangePasswordModel model)
{
}
The http type will map it back to its correct method in combination with the Route name.
I am not sure I follow as you have GET and POST right there in your code, but in any case you have other options:
Option 1
First, you can configure your custom Routes in the App_Start folder in the WebApiConfig.cs file. Here is what I normally use:
// GET /api/{resource}/{action}
config.Routes.MapHttpRoute(
name: "Web API RPC",
routeTemplate: "{controller}/{action}",
defaults: new { },
constraints: new { action = #"[A-Za-z]+", httpMethod = new HttpMethodConstraint("GET") }
);
// GET|PUT|DELETE /api/{resource}/{id}/{code}
config.Routes.MapHttpRoute(
name: "Web API Resource",
routeTemplate: "{controller}/{id}/{code}",
defaults: new { code = RouteParameter.Optional },
constraints: new { id = #"\d+" }
);
// GET /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Get All",
routeTemplate: "{controller}",
defaults: new { action = "Get" },
constraints: new { httpMethod = new HttpMethodConstraint("GET") }
);
// PUT /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Update",
routeTemplate: "{controller}",
defaults: new { action = "Put" },
constraints: new { httpMethod = new HttpMethodConstraint("PUT") }
);
// POST /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Post",
routeTemplate: "{controller}",
defaults: new { action = "Post" },
constraints: new { httpMethod = new HttpMethodConstraint("POST") }
);
// POST /api/{resource}/{action}
config.Routes.MapHttpRoute(
name: "Web API RPC Post",
routeTemplate: "{controller}/{action}",
defaults: new { },
constraints: new { action = #"[A-Za-z]+", httpMethod = new HttpMethodConstraint("POST") }
);
I use a combination of RESTful endpoints as well as RPC endpoints. For some purists, this is grounds for a holy war. For me, I use a combination of the two because it is a powerful combination and I can't find any sane reason not to.
Option 2
As the others have pointed out and as I myself am doing more of these days, use attribute routing:
[HttpGet]
[GET("SomeController/SomeUrlSegment/{someParameter}")]
public int SomeUrlSegment(string someParameter)
{
//do stuff
}
I needed a NuGet package for attribute routing to make this work (just search NuGet for "Attribute Routing"), but I think that MVC 5/WebAPI 2 has it natively.
Hope this helps.
You could use attribute routing:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Some documentation to get you started:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
First Put this route to webapiconfig.cs
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now you can add actions to your controllers like this
[HttpPost]
public void Upload()
{
//Do some thing
}
I decorated upload action with httppost attribute that means this action accept just only post requests , if you want to actions to be GET , you can remove attribute or just decorate to your suite

UriPathExtensionMapping in WebAPI 2

Weird stuff is going on when I try to add extension mapping features to my api. Some things work but I cant get anything to properly return JSON. These related questions haven't gotten me where I need to go:
UriPathExtensionMapping to control response format in WebAPI
UriPathExtensionMapping in MVC 4
My project has both HttpRoutes and HttpAttributeRoutes enabled. Not sure if that matters - I am just using the default WebApi project template. I've got the following routes:
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "Api UriPathExtension",
routeTemplate: "api/{controller}.{ext}",
defaults: new { }
);
config.Routes.MapHttpRoute(
name: "Api UriPathExtension ID 1",
routeTemplate: "api/{controller}.{ext}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Api UriPathExtension ID 2",
routeTemplate: "api/{controller}/{id}.{ext}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Here's my controller:
[RoutePrefix("api/roundTypes")]
public class RoundTypesController : ApiController
{
// GET api/roundTypes
[Route("")][HttpGet]
public IQueryable<Vcijis.RoundType> GetAllRoundTypes()
When I test it:
http://localhost/api/roundTypes **works** but is XML
http://localhost/api/roundTypes/ **works** (also XML)
http://localhost/api/roundTypes.json returns **404**
http://localhost/api/roundTypes.json/ returns a **JSON formatted error**
The JSON error message I get is:
{"message":"No HTTP resource was found that matches the request URI
'http://localhost/api/roundTypes.json/'.",
"messageDetail":"No action was found on the controller 'RoundTypes'
that matches the request."}
I've also tried with an id parameter and get similar results. I can't seem to get {ext} working in HttpAttributeRoutes at all. Help?
Attributed controllers/actions cannot be reached from routes matched to conventional ones. So you would need to use attribute routing to specify the {ext} in your route templates.
One example:
[RoutePrefix("api/customers")]
public class CustomersController : ApiController
{
[Route("~/api/customers.{ext}")]
[Route]
public string Get()
{
return "Get All Customers";
}
[Route("{id}.{ext}")]
[Route("{id}")]
public string Get(int id)
{
return "Get Single Customer";
}
[Route]
public string Post(Customer customer)
{
return "Created Customer";
}
[Route("{id}")]
public string Put(int id, Customer customer)
{
return "Updated Customer";
}
[Route("{id}")]
public string Delete(int id)
{
return "Deleted Customer";
}
}

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