Multiple actions were found: /resource/id vs /resource/id/action - c#

I realize this is a common question, but I can't find one that properly asks the same question with my configuration.
I have a default route that looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
);
And my controller looks like this:
public class VariablesController : ApiController
{
public IEnumerable<Variable> Get()
{
// get all
}
public Variable Get( int id )
{
// get individual
}
[HttpGet]
public DistributionReport GetDistribution( int id )
{
// get distribution report
}
}
When I GET /api/variables/1/getDistribution, it works fine.
When I GET /api/variables/1, I get a 500 exception:
Multiple actions were found that match the request:
Models.Variable Get(Int32) on type Controllers.Api.VariablesController
Models.DistributionReport GetDistribution(Int32) on type Controllers.Api.VariablesController
What do I need to do to modify my situation so they both work?

Related

ASP.Net MVC 4 API custom method name

I have a problem that I want to call a MVC Api method with a custom name.
I changed the WebApi.config as described here
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id="test" }
);
and wrote a class
public class MissingCardBoxModelController : ApiController
{
// GET api/missingcardboxmodel
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/missingcardboxmodel/5
public string Get(string id)
{
return id;
}
public string GetTrackingNumber(string parcelLabelNumber)
{
string trackingNumber = "some number";
return trackingNumber;
}
// POST api/missingcardboxmodel
public void Post([FromBody]string value)
{
}
// PUT api/missingcardboxmodel/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/missingcardboxmodel/5
public void Delete(int id)
{
}
}
But I can't call the method via http://localhost:58528/api/MissingCardBoxModel/GetTrackingNumber/123456
I Get the message
No action was found on the controller 'MissingCardBoxModel' that
matches the request.
Why can't I call the method ?
If your routes are configured to be these (default in the MVC solution template):
url: "{controller}/{action}/{id}"
You should change parcelLabelNumber to id.
You can read more about routes here.
By Default Web API allows Restful conventions that means it will auto map GET, PUT, POST, DELETE etc action names. if you look inside your WebApiConfig in routes it only allows the route below
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
which means it only allows
.../api/yourcontrollername/a parameter that will map to id
.
you basically have 2 options, one to use attribute routing. or you can add a route to your custom method eg:
config.Routes.MapHttpRoute(
name: "custom",
routeTemplate: "api/{controller}/{action}/{parcelLabelNumber}",
defaults: new { parcelLabelNumber = "" }
);
please also notice the parameter name here "parcelLabelNumber", you have to name your parameter same here as in your action. You should be able to reach this action at - http://localhost:23691/api/MissingCardBoxModel/GetTrackingNumber/1245
Also please have a look at Routing in general

Web API: Multiple actions were found that match the request [duplicate]

I keep getting this error when I try to have 2 "Get" methods
Multiple actions were found that match the request: webapi
I been looking around at the other similar questions about this on stack but I don't get it.
I have 2 different names and using the "HttpGet" attribute
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Your route map is probably something like this in WebApiConfig.cs:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
But in order to have multiple actions with the same http method you need to provide webapi with more information via the route like so:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Notice that the routeTemplate now includes an action. Lots more info here: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Update:
Alright, now that I think I understand what you are after here is another take at this:
Perhaps you don't need the action url parameter and should describe the contents that you are after in another way. Since you are saying that the methods are returning data from the same entity then just let the parameters do the describing for you.
For example your two methods could be turned into:
public HttpResponseMessage Get()
{
return null;
}
public HttpResponseMessage Get(MyVm vm)
{
return null;
}
What kind of data are you passing in the MyVm object? If you are able to just pass variables through the URI, I would suggest going that route. Otherwise, you'll need to send the object in the body of the request and that isn't very HTTP of you when doing a GET (it works though, just use [FromBody] infront of MyVm).
Hopefully this illustrates that you can have multiple GET methods in a single controller without using the action name or even the [HttpGet] attribute.
Update as of Web API 2.
With this API config in your WebApiConfig.cs file:
public static void Register(HttpConfiguration config)
{
//// Web API routes
config.MapHttpAttributeRoutes(); //Don't miss this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
}
You can route our controller like this:
[Route("api/ControllerName/Summary")]
[HttpGet]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[Route("api/ControllerName/FullDetails")]
[HttpGet]
public HttpResponseMessage FullDetails()
{
return null;
}
Where ControllerName is the name of your controller (without "controller"). This will allow you to get each action with the route detailed above.
For further reading: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
In Web API (by default) methods are chosen based on a combination of HTTP method and route values.
MyVm looks like a complex object, read by formatter from the body so you have two identical methods in terms of route data (since neither of them has any parameters from the route) - which makes it impossible for the dispatcher (IHttpActionSelector) to match the appropriate one.
You need to differ them by either querystring or route parameter to resolve ambiguity.
After a lot of searching the web and trying to find the most suitable form for routing map
if have found the following
config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id =RouteParameter.Optional }, new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
These mapping applying to both action name mapping and basic http convention (GET,POST,PUT,DELETE)
This is the answer for everyone who knows everything is correct and has checked 50 times.....
Make sure you are not repeatedly looking at RouteConfig.cs.
The file you want to edit is named WebApiConfig.cs
Also, it should probably look exactly like this:
using System.Web.Http;
namespace My.Epic.Website
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// api/Country/WithStates
config.Routes.MapHttpRoute(
name: "ControllerAndActionOnly",
routeTemplate: "api/{controller}/{action}",
defaults: new { },
constraints: new { action = #"^[a-zA-Z]+([\s][a-zA-Z]+)*$" });
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
I could have saved myself about 3 hours.
It might be possible that your webmethods are being resolved to the same url. Have a look at the following link :-
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
So, you might need to add your methodname to your routing table.
Without using actions the options would be:
move one of the methods to a different controller, so that they don't clash.
use just one method that takes the param, and if it's null call the other method from your code.
This solution worked for me.
Please place Route2 first in WebApiConfig. Also Add HttpGet and HttpPost before each method and include controller name and method name in the url.
WebApiConfig =>
config.Routes.MapHttpRoute(
name: "MapByAction",
routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
Controller =>
public class ValuesController : ApiController
{
[HttpPost]
public string GetCustomer([FromBody] RequestModel req)
{
return "Customer";
}
[HttpPost]
public string GetCustomerList([FromBody] RequestModel req)
{
return "Customer List";
}
}
Url =>
http://localhost:7050/api/Values/GetCustomer
http://localhost:7050/api/Values/GetCustomerList
I found that that when I have two Get methods, one parameterless and one with a complex type as a parameter that I got the same error. I solved this by adding a dummy parameter of type int, named Id, as my first parameter, followed by my complex type parameter. I then added the complex type parameter to the route template. The following worked for me.
First get:
public IEnumerable<SearchItem> Get()
{
...
}
Second get:
public IEnumerable<SearchItem> Get(int id, [FromUri] List<string> layers)
{
...
}
WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{layers}",
defaults: new { id = RouteParameter.Optional, layers RouteParameter.Optional }
);
It is possible due to using MVC controller instead of Web API controller.
Check the namespace in Web API controller it should be as following
using System.Net;
using System.Net.Http;
using System.Web.Http;
If the namespace are as following then it is give above error in web api controller method calling
using System.Web;
using System.Web.Mvc;
Please check you have two methods which has the different name and same parameters.
If so please delete any of the method and try.
I've stumbled upon this problem while trying to augment my WebAPI controllers with extra actions.
Assume you would have
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
[HttpGet]
public void ReSeed()
{
// Your custom action here
}
There are now two methods that satisfy the request for /api/controller which triggers the problem described by TS.
I didn't want to add "dummy" parameters to my additional actions so I looked into default actions and came up with:
[ActionName("builtin")]
public IEnumerable<string> Get()
{
return this.Repository.GetAll();
}
for the first method in combination with the "dual" route binding:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "builtin", id = RouteParameter.Optional },
constraints: new { id = #"\d+" });
config.Routes.MapHttpRoute(
name: "CustomActionApi",
routeTemplate: "api/{controller}/{action}");
Note that even though there is no "action" parameter in the first route template apparently you can still configure a default action allowing us to separate the routing of the "normal" WebAPI calls and the calls to the extra action.
In my Case Everything was right
1) Web Config was configured properly
2) Route prefix and Route attributes were proper
Still i was getting the error. In my Case "Route" attribute (by pressing F12) was point to System.Web.MVc but not System.Web.Http which caused the issue.
You can add [Route("api/[controller]/[action]")] to your controller class.
[Route("api/[controller]/[action]")]
[ApiController]
public class MySuperController : ControllerBase
{
...
}
I know it is an old question, but sometimes, when you use service resources like from AngularJS to connect to WebAPI, make sure you are using the correct route, other wise this error happens.
Make sure you do NOT decorate your Controller methods for the default GET|PUT|POST|DELETE actions with [HttpPost/Put/Get/Delete] attribute. I had added this attibute to my vanilla Post controller action and it caused a 404.
Hope this helps someone as it can be very frustrating and bring progress to a halt.
For example => TestController
[HttpGet]
public string TestMethod(int arg0)
{
return "";
}
[HttpGet]
public string TestMethod2(string arg0)
{
return "";
}
[HttpGet]
public string TestMethod3(int arg0,string arg1)
{
return "";
}
If you can only change WebApiConfig.cs file.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/",
defaults: null
);
Thats it :)
And Result :
Have you tried like:
[HttpGet("Summary")]
public HttpResponseMessage Summary(MyVm vm)
{
return null;
}
[HttpGet("FullDetails")]
public HttpResponseMessage FullDetails()
{
return null;
}

REST webapi URI GET with string instead of id not routing as expected

I have the following example where the request is http://{domain}/api/foo/{username} but I get a 404 status code back. No other Get actions exist on this controller. Shouldn't this work?
public class FooController : ApiController
{
public Foo Get(string username)
{
return _service.Get<Foo>(username);
}
}
By default your route will look something like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
When you visit the url http://{domain}/api/foo/{username} the controller is mapped as foo and the optional id parameter is mapped to {username}. As you don't have a Get action method with a parameter called id a 404 is returned.
To fix this you can either call the API method by changing the URL to be explicit about the parameter name:
http://{domain}/api/foo?username={username}
Or you could change your parameter name in your action method:
public Foo Get(string id)
{
var foo = _service.Get<Foo>(username);
return foo;
}
Or you could change your route to accept a username:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{username}",
defaults: new { username = RouteParameter.Optional }
);

Why do my WebAPI RESTful routes throw 404 errors?

On a side-project I am working on, I am creating a RESTful API using WebAPI 2.2. The thing I'm working on is a means of accessing settings for a game. An example of the kind of routes I am trying to accomplish are as follows:
http://x/api/GameSettings/ <-- Returns all settings
http://x/api/GameSettings/audio <-- Returns the 'audio' category
http://x/api/GameSettings/audio/volume <-- Returns the key 'volume' in category audio
Note: the examples are all Get requests.
I've implemented the following controller...
public class GameSettingsController : ApiController
{
// GET /api/GameSettings
public HttpResponseMessage Get()
{
// Magic
return Request.CreateResponse(HttpStatusCode.OK, model);
}
public HttpResponseMessage Get(string category)
{
// Similar.
}
public HttpResponseMessage Get(string category, string key)
{
// Slightly different, but still similar.
}
}
I bound up the following MVC routes:
// Only necessary for the main view...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
And, I bound up the following WebAPI routes:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ApiGeneralCommand",
routeTemplate: "api/{controller}",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiCategoryCommands",
routeTemplate: "api/{controller}/{category})",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiKeyCommands",
routeTemplate: "api/{controller}/{category}/{key}",
defaults: new { controller = "GameSettings", category = "master" },
constraints: new { key = "[a-z0-9.-]" }
);
...And finally, my Global.asax configuration is set up like so:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
...But one small problem.
When I navigate to http://x/api/GameSettings/audio, I get a 404 error. It's as if the category argument in the request isn't being properly associated to the Get(string category) method on my controller. This leads me to believe my routes are wrong or I'm missing something.
As a sanity check, I tested the route using a non-RESTful syntax, http://x/api/GameSettings?category=audio, which hit a breakpoint and yielded a result. This only reaffirms my theory that the WebAPI routing is off.
As an additional sanity check, I tested http://x/api/GameSettings/ and not only hit a breakpoint set in that function, but returned the expected result.
Question: What is my routing missing, that will allow http://x/api/GameSettings/audio to work like http://x/api/GameSettings?category=audio? I haven't worked with a RESTful API in a while, so I'm sure I'm missing something really dumb.
I would try to use the attribute routing. I believe that should work well for your scenario.
[RoutePrefix("api/GameSettings")]
public class GameSettingsController
{
// GET /api/GameSettings
[HttpGet]
public HttpResponseMessage Get()
{
// Magic
return Request.CreateResponse(HttpStatusCode.OK, model);
}
[Route("{category}")]
[HttpGet]
public HttpResponseMessage Get(string category)
{
// Similar.
}
[Route("{category}/{key}")]
[HttpGet]
public HttpResponseMessage Get(string category, string key)
{
// Slightly different, but still similar.
}
}
I would remove the stuff that you have added to the config.
Hope this helps.
Change the order and try.Because ASP.NET realizes that you have three routes. It will check the top-most route first and if your data can be placed in that route it will not check any more routes.
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ApiKeyCommands",
routeTemplate: "api/{controller}/{category}/{key}",
defaults: new { controller = "GameSettings", category = "master" },
constraints: new { key = "[a-z0-9.-]" }
);
config.Routes.MapHttpRoute(
name: "ApiCategoryCommands",
routeTemplate: "api/{controller}/{category})",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiGeneralCommand",
routeTemplate: "api/{controller}",
defaults: new { controller = "GameSettings" }
);

API multiple Get methods and routing

I have a controller with only Get Methods
public class DeviceController : ApiController
{
List<Device> machines = new List<Device>();
public IEnumerable<Device> GetAllMachines()
{
//do something
return machines;
}
[HttpGet]
public IEnumerable<Device> GetMachineByID(int id)
{
//do something
return machines;
}
[HttpGet]
public IEnumerable<Device> GetMachinesByKey(string key)
{
//do something
return machines;
}
}
I would like to be able to access these via URL and get the data back
../api/{contorller}/GetAllMachines
../api/{contorller}/GetMachineByID/1001
../api/{contorller}/GetMachiesByKey/HP (machines exist)
When i run the first two in IE developer mode (f12) i get Json back displaying all machines and machine 1001. However when i run GetMachinesByKey/HP i get 404 error.
Also my WebApiConfig looks like this
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{Action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Anyone enlighten me as to what I am doing wrong?
The routing engine is expecting to bind to a variable named id as defined in the route config:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{Action}/{id}", //<--- here {id} means bind to parameter named 'id'
defaults: new { id = RouteParameter.Optional }
);
In your action GetMachinesByKey(string key) parameter is named key and so the framework isn't connecting those dots for you.
You could pass the parameter in the querystring so using a URL of the form /api/{contorller}/GetMachiesByKey/?key=HP will bind correctly (you may need to change the route config as this doesn't pass an id parameter that the current config will be expecting).
Alternatively I believe you can specify a route for an action using attribute routing. This allows you to decorate your action method with an attribute which tells the framework how the route should be resolved e.g:
[Route("<controller>/GetMachinesByKey/{key}")]
public IEnumerable<Device> GetMachinesByKey(string key)
Use the RoutePrefix and Route attributes.
[RoutePrefix("api/device")]
public class DeviceController : ApiController
{
List<Device> machines = new List<Device>();
[HttpGet]
[Route("Machines")]
public IEnumerable<Device> GetAllMachines()
{
//do something
return machines;
}
[HttpGet]
[Route("Machines/{id:int}")]
public IEnumerable<Device> GetMachineByID(int id)
{
//do something
return machines;
}
[HttpGet]
[Route("Machines/{key}")]
public IEnumerable<Device> GetMachinesByKey(string key)
{
//do something
return machines;
}

Categories

Resources