Overload WebAPI controller action doesn't work with viewmodels? - asp.net-mvc

The following works:
public class UsageController : ApiController
{
public int GetMilk(string param1, string param2)
{
return -1;
}
public string GetBilling(string param1)
{
return null;
}
}
But the following throws a "Multiple actions were found that match the request" Exception?!?!
public class UsageController : ApiController
{
public int GetMilk(MilkVM vm)
{
return -1;
}
public string GetBilling(BillingVM vm)
{
return null;
}
}
How can I fix this?

By default, ASP.NET Web API selects an action method based on the HTTP verb and the action method parameters. In the second case, you have two action methods that can handle GET and they have one parameter each. When Web API tries to find an action method (more info here) for your GET, it will find both the methods.
You can follow RPC-style URI if you must have action methods like these. Add a mapping in WebApiConfig.cs like this.
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "RpcStyle",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Then, make GET to URIs with action methods like so: http://localhost:port/api/usage/getmilk?a=1&b=2.
BTW, your action methods will not be able to bind the query string to a complex type by default. Use FromUri attribute like this: public int GetMilk([FromUri]MilkVM vm). GET requests must not have a request body and hence complex types will not be bound by default.

Related

Conflicts with routing when using default {controller}/{id} route and {controller}/{action}/{id} route

I have the following routes setup;
RouteTable.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional },
constraints: null,
handler: new WebApiMessageHandler(GlobalConfiguration.Configuration));
and the following controller setup;
public class GetFileController : ApiController
{
[HttpGet]
public HttpResponseMessage Get(string id)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
The issue I have here is that this url
/api/GetFile/id_is_a_string
returns this error;
<Error>
<Message>
No HTTP resource was found that matches the request URI /api/GetFile/id_is_a_string.
</Message>
<MessageDetail>
No action was found on the controller 'GetFile' that matches the name 'id_is_a_string'.
</MessageDetail>
</Error>
Is there any way to get around having it not think the string parameter is actually the action name?
I know I could change my request URL to be;
/api/GetFile?id=id_is_a_string
but this routing change affects a lot of other controllers I already have set and don't really wish to go through everything to switch it up to send the request this way.
If I re-order the routes, it seems to work as it did but for my new controller which I would've ideally liked to have multiple endpoints within, I get this error;
ExceptionMessage=Multiple actions were found that match the request:
New Controller
public class GettingThingsController : ApiController
{
[HttpPost]
public IHttpActionResult GetPeople()
{
return Ok();
}
[HttpPost]
public IHttpActionResult GetProducts()
{
return Ok();
}
}
Is there anyway of achieving what I need at all?!
You can try to specify parameters with Regex:
routeTemplate: "api/{controller}/{action:regex([a-z]{1}[a-z0-9_]+)}/{id:regex([0-9]+)}",
routeTemplate: "api/{controller}/{id:regex([0-9]+)}",
These regex works in Route attribute. You can test it in RouteTable mappings.

MVC Catch-All route is the only defined route, but it gives me 404

This is my Register method for routes in my web API project
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "AllRoutes",
routeTemplate: "{*url}",
defaults: new { controller = "IncomingRequest", action = "ProcessRequest" });
I would expect everything to go my ProcessRequest method on my IncomingRequest controller. However all routes result in 404. e.g.
http://localhost/CatCatcher/Cat/3
Can anyone advise what I might have missed?
Try this:
routes.MapRoute(
"AllRoutes",
"{*id}",
new { controller = "IncomingRequest", action = "ProcessRequest", id = "" }
);
Sorry, i've tested it now....
In web API project you cant set routes by different ways like:
config.Routes.MapHttpRoute(
name: "Routes",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And to achieve URL ("http://localhost/CatCatcher/Cat/3") you can set route prefix on controller and action also Here is example for that:
[RoutePrefix("CatCatcher")]
public class CatCatcherController : ApiController
{
[HttpGet]
[Route("Cat")]
public object Cat(int Id){
//Do something
}
}
And if you want to go on Execution before hitting Action then lets follow the example in which I secure API with IP/Domain here it is:
public class AuthorizeIPAddressAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
//Even you can do here what you want
//Get users IP Address
string ipAddress = HttpContext.Current.Request.UserHostAddress;
if (!IsIpAddressValid(ipAddress.Trim()))//check allow ip
{
//Send back a HTTP Status code of 403 Forbidden
filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.NotFound);
}
base.OnActionExecuting(filterContext);
}
After Adding this class in project you just have to do this on your controller:
[AuthorizeIPAddress]
[RoutePrefix("CatCatcher")]
public class CatCatcherController : ApiController
{
[HttpGet]
[Route("Cat")]
public object Cat(int Id){
//Do something
}
}
Hopefully this will help you..

Web api different named actions cause Multiple actions error

I have web api 2 controller actions:
[HttpGet]
public Response<IEnumerable<Product>> Get()
{
....(Get all products)
}
[HttpGet]
public Response<Product> Get(int id)
{
....(Get product by id)
}
[HttpGet]
public Response<IEnumerable<Product>> Category(int id)
{
.... (Get products by category)
}
I want to use this controllers with url:
http://localhost/api/product
http://localhost/api/product/1
http://localhost/api/product/category/1
But this url http://localhost/api/product/1 returns error,
Multiple actions were found that match the request
My config settings are like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You might be better off using attribute routing rather than global routing here. If you remove your global routes and define your routes on a per-action basis you should have no problems. For example, your routes could look like:
[Route("api/product")]
[Route("api/product/{id:int}")]
[Route("api/product/category/{id:int}")]
This is the default controller created when you create a new ASP.NET Web APi within Visual Studio:
[Authorize]
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)
{
}
}
And the WebApi config:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Your two Get methods match the same route. I would delete the first Get method and change your second Get method to use an optional id parameter like so:
[HttpGet]
public Response<IEnumerable<Product>> Get(int? id)
{
// Get all products if id is null, else get product by id and return as a list with one element
}
This way, Get will match routes for both "product" and "product/1".

MVC Web Api calling method not working properly

I am trying to implement three methods controller
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public IEnumerable<string> Get(int id)
{
return new string[] { "sa1", "sa2" };
}
[HttpGet]
public IEnumerable<string> password()
{
return new string[] { "password", "password" };
}
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But When i try to call http://localhost:49365/api/mycontrollername/password
it's always showing The request is invalid.
if you want to call an api function like "http://localhost:49365/api/mycontrollername/password"
you have to add ActionName attribute on the controller and add route on the Routeconfig.cs;
here is the example
[ActionName("GetEmployees")]
public IEnumerable<Employee> GETEmployees()
{
return _db.Employees.AsNoTracking();
}
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "Employees", action = "GetEmployees", id = UrlParameter.Optional }
);
I suspect it is attempting to call Get(int id) and then trying to pass the word "password" as the integer parameter. From what I can recall it's down to convention-over-configuration in that when you make a GET request it looks for a method named Get. In this case it finds one. Then based on the routing, i.e. after the controller name comes an ID, it looks at the next part of the URL, in this case "password", and then uses that as a value for the id argument.
If you were to remove the two Get methods you probably find that your URL works, but if you add other HttpGet methods you will run into other issues related to "multiple actions found". The following discussion may help if you decide you need to have multiple HttpGet methods:
Web Api Routing for multiple Get methods in ASP.NET MVC 4

ASP.NET MVC routing doesn't use method name to find the action and just uses the parameters

Consider an Api controller like this:
public class MyApiController
{
[HttpGet]
public IEnumerable<object> GetItems(int from, int count)
{
...
}
[HttpGet]
public IEnumerable<object> GetActiveItems(int from, int count)
{
...
}
}
If I call /MyApi/GetActiveItems/?from=0&count=20 then it's possible to route the action GetItems instead of GetActiveItems because of parameters similarity.
If I change the parameters name, for example (int fromActive, int countActive) it works properly.
Why is that so? Why doesn't it use the action name to match with the method name?
Should I do something in the routing?
It seems the problem was about a bad routing set somewhere other than its usual:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and after then there was the correct routing:
routes.MapHttpRoute(
name: "DefaultProvider",
routeTemplate: "api/{controller}/{action}"
);
In this case, as I haven't used {action} in the first routing, the action name goes to the {id} and the routing tries to resolve action by its parameters.

Resources