Web api different named actions cause Multiple actions error - asp.net-mvc

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".

Related

MVC 5 Web Attribute Routing is not working

I implemented the Attribute routing in my Application and after that when I started nothing was working as planned. Only the Json Results works rest is not working as expected.
[RoutePrefix("ProductCategory")]
public class CategoryController : Controller
{
[Route("CategoryMain")]
// GET: /Category/
public ActionResult Index()
{
var cat = categoryService.GetCategories();
if (Request.IsAjaxRequest())
{
return PartialView("Index", cat);
}
return View("Index", cat);
}
}
Error
Server Error in '/' Application. The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /ProductCategory/MainIndex
I've also tried with Just Index even that is not working now
But if The method is JsonResult it will return me the data in json format. Its not working on any other ActionResults
My RouteConfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Category", action = "Index", id = UrlParameter.Optional }
);
}
My webapiConfig
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
My Global
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Autofac and Automapper configurations
Bootstrapper.Run();
Given the route prefix and route below
[RoutePrefix("ProductCategory")]
public class CategoryController : Controller {
[HttpGet]
[Route("CategoryMain")] // Matches GET ProductCategory/CategoryMain
public ActionResult Index() {
var cat = categoryService.GetCategories();
if (Request.IsAjaxRequest()) {
return PartialView("Index", cat);
}
return View("Index", cat);
}
}
The requested URL needs to be
ProductCategory/CategoryMain
for the CategoryController.Index Action

Web APi Routing Understanding

I have created a new controller called WebPortalController but when I call it or try to call it via the browser I couldnt access the below method just said resource is not found. Do I need to add a new routing to the RoutesConfig.cs code if so how.?
namespace WebApi.Controllers
{
public class WebPortalController : ApiController
{
// GET api/webportal
private WebPortEnterpriseManagementDa _da = new WebPortEnterpriseManagementDa();
public ManagedType Get(string name)
{
ManagedType items = _da.GetManagedType(name);
return items;
}
// POST api/webportal
public void Post([FromBody]string value)
{
}
// PUT api/webportal/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/webportal/5
public void Delete(int id)
{
}
}
}
Routes file
namespace WebApi
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The route config you have shown is for MVC routes and is located in the App_Start/RouteConfig file. Check that you have a default API route set in your App_Start/WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then you will need to change the parameter name in your Get method to match the routing parameter:
public ManagedType Get(string id)
{
ManagedType items = _da.GetManagedType(name);
return items;
}
This should allow you to call your Get method through a browser using:
localhost:55304/api/WebPortal/Test
In order to test out your Post/Put/Delete methods you will need to use Fiddler or a browser addin such as Postman

How does routing know where the file is?

My VS project has the following folder and files:
~\Controllers
\AccountController.cs
\HomeController.cs
...
~\Data
\AccountController.cs
...
~\App_Start
\RouteConfig.cs
\WebApiConfig.cs
WebApiConfig.cs contains:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
~\Data\AcccountController.cs contains:
namespace myApp.Data
{
public class AccountController : ApiController
{
[HttpGet]
public string GetUser(int id)
{
//...
}
...
}
}
When I make a http call to /api/Account/GetUser, the call is routed to the GetUser method shown above. What in all of the above or any configuration file tells the server to take the action from this particular file? What if ~/Controllers/AccountController.cs also contain a method of the same name?
It is called convention over configuration.

Overload WebAPI controller action doesn't work with viewmodels?

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.

MVC 3 Web API Routing Not Working

I'm fighting issues with routing in an MVC 3 Web API. It seems like it should be pretty simple, but I'm not making any headway.
My error is:
<Error>
<Message>The request is invalid.</Message>
<MessageDetail>
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'Boolean NewViolationsPublished(Int32)' in 'BPA.API.Controllers.CacheManagementController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
</MessageDetail>
</Error>
My RegisterRoutes is such:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache");
routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);
routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"^\d+$" } // Only integers
);
routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^\d+$" } // Only integers
);
}
And my controller is (i took out the code for ease of reading):
public class CacheManagementController : ApiController
{
public CacheManagementController()
[HttpGet]
public bool NewViolationsPublished(int id)
[HttpGet]
public bool IsCached(CachedItemLabels label, int clientId)
[HttpGet]
public void RemoveItemFromCache(int clientId, CachedItemLabels cacheLabel, string test)
[HttpGet]
public string NukeAllItemsFromCache()
}
I get the error when I try to call:
http://localhost/api/CacheManagement/NukeAllItemsFromCache
TIA
Are you sure you're defining your routes in the right place? Web API routes should be configured using System.Web.Http.GlobalConfiguration.Configuration.Routes.MapHttpRoute (normally in a WebApiConfig.cs file in App_Start), as shown on http://www.asp.net/web-api/overview/extensibility/configuring-aspnet-web-api, which is different from the route configuration used by vanilla MVC.
I think you may just be hitting the default Web API route, which is api/{controller}/{id}, with ID optional. The action is determined by the HTTP verb, which in this case is GET, and all of your methods match that by virtue of the [HttpGet].
Edit: example
Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalModifiers.ApplyGlobalConfiguration(this, true);
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "action-specific",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I beleave your problem is in this route:
routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"^\d+$" } // Only integers
);
You specify id as an optional parameter and at the same time constrain it to actually be a non-nullable number. If /api/CacheManagement/NukeAllItemsFromCache is an exception, then add additional route for it, otherwise ease a constraint to something like #"^\d*$" if possible.
You are missing the default action for the route you configured:
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache");
should be
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache",
defaults: new { action = "NukeAllItemsFromCache" }
);
You tried to put parameter id as nullable like:
[HttpGet]
public bool NewViolationsPublished(int? id)
or
[HttpGet]
public bool NewViolationsPublished(Nullable<Int32> id)
Maybe this works...
Double check that your controller extends ApiController rather than Controller
public class CacheManagementController : ApiController
rather than
public class CacheManagementController : Controller

Resources