I have applied attribute routing on my controller and it'srouting to wrong action. I don't know where I am getting it wrong.
Here is my controller:
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Description;
using System.Linq;
using System;
namespace Iboo.API.Controllers
{
public class ClientsController : ApiController
{
private readonly IClientRepository _repository;
public ClientsController(IClientRepository repository)
{
_repository = repository;
}
// GET: api/Clients
[Route("api/v1/clients")]
public IEnumerable<Client> Get()
{
//code
}
// GET: api/Clients/5
[HttpGet]
[ResponseType(typeof(Client))]
[Route("api/v1/clients/get/{id}")]
public IHttpActionResult GetClientById(int id)
{
//code
}
// GET: api/Clients/5
[HttpGet]
[ResponseType(typeof(string))]
[Route("api/v1/clients/{id}/emailid")]
public IHttpActionResult GetClientEmailId(int id)
{
//code
}
}
}
I am specifically interested in the GetClientEmailId method. Below is my WebApiConfig
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var container = new UnityContainer();
container.RegisterType<IClientRepository, ClientRepository>(new
HierarchicalLifetimeManager());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
My Global.asax.cs is as follows
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
In the browser If I type http://localhost:54919/api/v1/clients/?id=1/getemailid it's taking me to http://localhost:54919/api/v1/clients which is not what I want.
If I try http://localhost:54919/api/v1/clients/1/getemailid I am getting a 404 error.
I am not sure as to what I'm getting wrong.
You are calling the wrong URLs according to routes on the actions. you get 404 because the URL you call does not match to any of the route templates you have on your actions
[RoutePrefix("api/v1/clients")]
public class ClientsController : ApiController {
//...other code removed for brevity
[HttpGet]
[Route("")] //Matches GET api/v1/Clients
public IHttpActionResult Get() {
//code
}
[HttpGet]
[ResponseType(typeof(Client))]
[Route("{id:int}")] //Matches GET api/v1/Clients/5
public IHttpActionResult GetClientById(int id) {
//code
}
[HttpGet]
[ResponseType(typeof(string))]
[Route("{id:int}/emailid")] //Matches GET api/v1/Clients/5/emailid
public IHttpActionResult GetClientEmailId(int id) {
//code
}
}
Take note of the expected URLs in the comments
You should also read up on Attribute Routing in ASP.NET Web API 2 to get a better understanding of how to do attribute-routing.
You can try using the route prefix on the controller.
[RoutePrefix("api/v1/clients")]
public class ClientsController : ApiController
{
// GET: api/Clients/5
[ResponseType(typeof(string))]
[Route("{id:int}/emailid"),HttpGet]
public IHttpActionResult GetClientEmailId(int id)
{
//code
}
}
You said:
In the browser If I type http://localhost:54919/api/v1/clients/?id=1/getemailid it's taking me to http://localhost:54919/api/v1/clients which is not what I want.
From the way your routes are set up, it looks like you need to go to http://localhost:54919/api/v1/client/1/emailid to get to the route you want
To explain the difference, when you call http://localhost:54919/api/v1/clients/?id=1/getemailid the route that would match that is something like:
[Route("api/v1/clients")]
public IHttpActionResult GetClientEmailId(string id)
{
//code
}
because you've added the id parameter as a querystring parameter. In this case, the id argument would have a value of 1/getemailid which doesn't make much sense.
by using the route parameters (by replacing ?id=1/getemailid with 1/emailid) you will actually match the route you want to
Related
Typically you call an action of a controller like so http://hostname/MyController/MyAction
I have a requirement for my Web Api to have routes like this one:
http://hostname/MyController?action=MyAction, i.e., pass the action in url parameter.
My controller:
public class MyController : ApiController
{
[HttpGet]
[Route("WHAT SHOULD BE HERE??")]
public IHttpActionResult MyAction()
{
// some code
}
}
Any ideas how I can write such a routing?
You could try the following:
public class MyController : ApiController
{
[HttpGet]
[Route("MyController")]
public IHttpActionResult MyInnerController(String action)
{
switch(action)
{
case "MyAction":
return MyAction();
}
return BadRequest("Invalid action: " + action);
}
public IHttpActionResult MyAction()
{
return Ok();
}
}
Things will get more complicated if you require additional parameters.
After more than a year I can come back to this question and answer it myself.
The solution you can use here is to write your own ActionSelector - this is the class Web Api framework uses to select actions, by default it uses System.Web.Http.Controllers.ApiControllerActionSelector, which you can override.
So lets say your controller looks like this:
public class MyController : ApiController
{
[HttpGet]
public IHttpActionResult MyAction()
{
// some code
}
}
Then you can create your own action selector like this (the code might be improved I wrote it very quickly):
public class QueryParameterActionSelector : ApiControllerActionSelector
{
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
var mapping = GetActionMapping(controllerContext.ControllerDescriptor);
var parameters = controllerContext.Request.GetQueryNameValuePairs();
foreach (var parameter in parameters)
{
if (parameter.Key == "action")
{
if (mapping.Contains(parameter.Value))
{
// Provided that all of your actions have unique names.
// Otherwise mapping[parameter.Value] will return multiple actions and you will have to match by the method parameters.
return mapping[parameter.Value].First();
}
}
}
return null;
}
}
And then finally you have to register this action selector in WebApiConfig.Register method. It will look like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
config.Services.Replace(typeof(IHttpActionSelector), new QueryParameterActionSelector());
}
}
Now you can call your action like this http://hostname/controller?action=MyAction
I am debugging someone elses code that uses WebAPI. The WebApiConfig looks like this:
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/{controller}/{id}",
defaults: new {id = RouteParameter.Optional});
The service method that gets called looks like this:
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
}
The above code works, and the controller is correctly called when a request is made. But how does the routing work when attributes are not present in the controller?
edit 1:
Let's say I create a new controller called PlantController:
public class PlantController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
}
When I call the web service like this:
api/v2/plant/test
The PlantController is not called. However, when I call the device service, it works:
api/v2/device/test
ID is optionnal. So if you call this king of route : api/v2/device it will try to call a controller action with this signature :
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
// do stuff here...
}
}
This rule is true for all HTTP words ( Post, Put etc... )
One use of the http verb attributes is a quasi overload based on the http verb.
Image this function.
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
public IHttpActionResult Get()
{
// do stuff here...
}
}
In this example the Get(string id) overload of Get will be called when the http request type is a GET, all non GET request types will be routed to the other Get() method.
I have a ApiController with those GETs:
public class UsersController : ApiController
{
public IHttpActionResult GetUsers()
{
[...]
}
public IHttpActionResult GetUsers(guid ID)
{
[...]
}
[Route("api/Users/{CodeA}/{CodeB}")]
public IHttpActionResult GetUsers(string CodeA, string CodeB)
{
[...]
}
}
The routing in webapiconfig.cs is the standard one:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/",
defaults: new { id = RouteParameter.Optional}
);
Trying to use swaggerUI I get a 500 error, and by fiddler I get:
Not supported by Swagger 2.0: Multiple operations with path 'api/utenti' and method 'GET'. See the config setting - \"ResolveConflictingActions\" for a potential workaround"
If I remove the last GET method swagger parses the api correctly. I've read from many sources that the problem is solved by specifying a different route, and I've tried to achieve this by adding the Route attribute to the last action.
Can someone please point me out in the right direction?
Thank you in advance.
The following code worked for me:
[Route("users")]
public class UsersController : ApiController
{
[Route("")]
public IHttpActionResult GetUsers()
{
string test = "";
return Ok(test);
}
[Route("{id}")]
public IHttpActionResult GetUsers(Guid id)
{
string test = "";
return Ok(test);
}
[Route("{CodeA}/{CodeB}")]
public IHttpActionResult GetUsers(string CodeA, string CodeB)
{
string test = "";
return Ok(test);
}
}
I have this configuration in the HttpConfiguration
config.Routes.MapHttpRoute("Default", "api/{controller}");
config.Routes.MapHttpRoute("Another", "api/{controller}/{action}");
config.Routes.MapHttpRoute("WithKey", "api/{controller}/{action}/{key}");
For that reason I cannot access my controller like this
http://<host>/api/products (works)
http://<host>/api/products/1 (doesn't work)
So I added the annotation Route in the get method but it doesn't work
[RoutePrefix("products")]
public class ProductsController : ApiController
{
[HttpGet]
public IQueryable<IProduct> GetProducts()
{
return db.GetProducts();
}
//[Route("products/{productID}")] Tried. Doesn't work
//[Route("{productID:int}")] Tried. Doesn't work
[HttpGet]
public IProduct GetProduct(int productID)
{
return db.GetProduct(productID);
}
}
The only way to make it work is typing the address like this http://<host>/api/products?productID=1, but I'd really want to access with this url http://<host>/api/products/1.
I can add new routes in the http configuration but cannot modify the existing ones. And I don't want to affect existing controllers.
How can I solve this, please?
First ensure that attribute routing is enabled before convention-based routes.
config.MapHttpAttributeRoutes();
//...convention-based routes.
config.Routes.MapHttpRoute("Default", "api/{controller}");
//...other code removed for brevity
Next you want to update the attribute routes.
[RoutePrefix("api/products")]
public class ProductsController : ApiController {
//GET api/products
[HttpGet]
[Route("")]
public IQueryable<IProduct> GetProducts() {
return db.GetProducts();
}
//GET api/products/1
[HttpGet]
[Route("{productID:int}")]
public IProduct GetProduct(int productID) {
return db.GetProduct(productID);
}
}
I am having a problem when trying to have two different GET methods, one for retrieving a number of resources while one is for retrieving a specific resource.
Startup.cs
config.Routes.MapHttpRoute("DefaultAPI",
"api/{controller}/{action}",
new { id = RouteParameter.Optional });
Controller.cs
[RoutePrefix("api/Files")]
public class FileController : ApiController
{
// /api/Files/
[Authorize]
[Route("")]
public IHttpActionResult GetAll()
{
}
// /api/Files/Id/
[Authorize]
[Route("Id")]
public async Task<HttpResponseMessage> Get([FromBody] string id)
{
}
// /api/Files/Upload
[Authorize]
[HttpPost]
[Route("Upload")]
public async Task<HttpResponseMessage> Post()
{
}
// /api/Files/Delete
[Authorize]
[Route("Delete")]
public IHttpActionResult Delete([FromBody] string id)
{
}
This is new to me and I know I am also making a mistake with using both IHttpActionResult as well as HttpResponseMessage but I figured I would change that later on after I figure out the routing.
Error:
When Startup.cs has
"api/{controller}/{action}"
, it returns a 404 Not found,
when it is "api/{controller}/{id}", the error is:
Multiple actions were found that match the request: \r\nGetAll
If you are using attribute-based routing, you need to add the following code before you declare any WebAPI routes in Startup.cs:
config.MapHttpAttributeRoutes();