I have a asp.net web api, using attributes for routing on the controllers. There are no route attriutes on the action level. The route for accessing a resource is:
[Route("{id}"]
public MyApiController: ApiController
{
public HttpResponseMessage Get(Guid id)
{
// ...
}
}
My problem is that when I want to create a search controller, I'd like the URL to be
[Route("search")]
But this results in an error: Multiple controller types were found that match the URL. Is it possible to make sure the exact matching route is selected before the generic one?
Technically, the phrase search could be a valid ID for the first controller, but as {id} is a guid, this will never be the case, thus I'd like to select the controller with the exact matching route.
You can use Route constraints to do the job. For example you could constraint your ID route to accept only valid GUID's.
Here is an ID controller that accepts only GUID strings in the URL:
[System.Web.Http.Route("{id:guid}")]
public class MyApiController: ApiController
{
public HttpResponseMessage Get(Guid id)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
The Search controller would match to an url like "/search". Here is the Search controller:
[System.Web.Http.Route("search")]
public class SearchController : ApiController
{
public HttpResponseMessage Get()
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Constraints will prevent matching conflicts in the router.
Related
I have an API controller which have standard GET,POST and Delete actions.
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
//Get
[HttpPost]
public async Task Post([FromBody] TestUser testUser, string tempPassword, role = "Guest")
{
}
}
Now I am adding a new action using:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post(string email, List<string> roles)
{
}
When I am trying to call the API using postman ,
Type : POST
Endpoint : http://localhost/api/users/UpdateRole
Request body:
{
"email":"something#mail.com",
"roles":["S1","s3"]
}
But I am getting a 404 as response back. On server I can see ,
the application completed without reading the entire request body.
It seems that your overall route is /api/Users/api/Users/UpdateRoute because of how RouteAttribute works.
[Route("a")]
public class MyController
{
[Route("a/b")]
public IActionResult MyAction()
{
}
}
The above will have a route of /a/a/b because the action route is appended to the controller route in this case.
Your options are:
Change the controller route to [Route("[controller]/[action]")] and remove the action route, in which case the example above would become /MyController/MyAction
Change the action route to simply [Route("b")], in which case the full route would be a/b
Use an absolute path for the action route [Route("/a/b")], in which case the controller route would be ignored and the full route will simply be /a/b.
See here for more information about routing.
As for your issue with null values, ASP.NET Core is currently expecting email and roles as querystring parameters. Instead, you should create a model for your request body:
public class MyModel
{
public string Email { get; set; }
public List<string> Roles { get; set; }
}
And then change your action to accept it:
[HttpPost]
[Route("api/[controller]/UpdateRole")]
public async Task Post([FromBody]MyModel model)
{
}
Is it possible to change the Routing controller name in MVC? In MVC 5 I would have done this:
[RoutePrefix("MySpecialSauce")]
public class ProductsController : Controller
{
[Route("GetBy/{id}")]
public MyObject GetBy(int id)
{
return something(id);
}
}
Now all I can find is to use the default name of your controller:
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpGet("GetBy/{id}")]
public MyObject GetBy(int id)
{
return something(id);
}
}
I want to use a different name for my route than actual controller name. How do you do this?
You can do the same in Core
[Route("MySpecialSauce")]
public class ProductsController : Controller {
[HttpGet("GetBy/{id:int}")]//Matches GET MySpecialSauce/GetBy/5
public MyObject GetBy(int id) {
return something(id);
}
}
[controller] is a token replacement to help with route template. it is not mandatory.
Source Token replacement in route templates ([controller], [action], [area])
For convenience, attribute routes support token replacement by
enclosing a token in square-braces ([, ]). The tokens [action],
[area], and [controller] will be replaced with the values of the
action name, area name, and controller name from the action where the
route is defined.
I have two Controllers as follows:
[RoutePrefix("v1/user/something")]
public class SomethingsController : ApiController
{
[Route("{id}")]
[HttpGet]
[ResponseType(typeof(SomethingsViewModel))]
public async Task<IHttpActionResult> GetAsync([FromUri]int id)
{
}
}
[RoutePrefix("v1/user")]
public class UserController : ApiController
{
[Route("{id}")]
[HttpGet]
[Authorize(Roles = "Super Admin")]
public async Task<IHttpActionResult> GetByIdAsync([FromUri]int id)
{
}
}
Now by looking at the code above, I'd think that the following two routes are being created:
v1/user/something/{id}
v1/user/{id}
But unfortunately, for some reason, that is not the case. I keep getting the following exception message when trying to access one of the above routes:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types: MyProject.Api.Controllers.UserController, MyProject.Api.Controllers.SomethingsController
Please help me out in figuring what I might be doing wrong or which small detail am I missing out here.
Though their route prefix are different their resolved routes match. for example v1/user/{id} will match v1/user/something/{id} where id parameter arg in the first route will take something/{id}.
Route prefix and Route attributes combine to create a full route that is added to the route table.
In a case like this you will need to use constraints in order to better differentiate the routes.
[RoutePrefix("v1/user/something")]
public class SomethingsController : ApiController {
[Route("{id:int}")]
[HttpGet]
[ResponseType(typeof(SomethingsViewModel))]
public async Task<IHttpActionResult> GetAsync([FromUri]int id) { ... }
}
[RoutePrefix("v1/user")]
public class UserController : ApiController {
[Route("{id:int}")]
[HttpGet]
[Authorize(Roles = "Super Admin")]
public async Task<IHttpActionResult> GetByIdAsync([FromUri]int id) { ... }
}
So now with the int constraint something wont be mistaken for valid parameter for the UserController.GetByIdAsync action
Reference Attribute Routing in ASP.NET Web API 2: Route Constraints
Route Constraints
Route constraints let you restrict how the parameters in the route
template are matched. The general syntax is "{parameter:constraint}".
For example:
[Route("users/{id:int}"]
public User GetUserById(int id) { ... }
[Route("users/{name}"]
public User GetUserByName(string name) { ... }
Here, the first route will only be selected if the "id" segment of the
URI is an integer. Otherwise, the second route will be chosen.
If I have the following controller:
public class RetrievalController : ApiController
{
[Route("api/User")]
public HttpResponseMessage RetrieveSomethingKthx()
{
if (true)
{
return Request.CreateResponse(System.Net.HttpStatusCode.OK);
}
return Request.CreateResponse(System.Net.HttpStatusCode.NotFound);
}
}
Is there a way early on (such as in OWIN) to read the path. E.G.: Request.Path == "http://www.website.com/api/User" and therefore get Reflection Information about that method?
Note that I am not relying on the Route to have the Controller Name or "Action" Name necessarily. I assume something lives in the Web API Routing to pull method info based on the Route.
I have a controller to GET a resource(say, Employee), which has two properties (say, CategoryId and DepartmentId). I need to configure the routes to support the following URLs:
~/api/employees/1234 [to get the employee with employeeId=1234]
~/api/employees [to get all the employees]
~/api/employees?departmentid=1 [to get all the employees with departmentId=1]
and the controller code looks like this:
public IEnumerable<Employee> Get()
{
....
}
public IEnumerable<Employee> Get(int employeeId, int departmentId = -1, int categoryId = -1)
{
.....
}
How to configure routes for this controller?
Thanks
for any querystyring parameter, there's nothin to do on the routing side: just have the controller method parmater matching with the qs parameter name(case insensitive.)
If instead your method parameter refers to a uri segment, method paramenter name has to match with route parameter/segment between curly brackets
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "/api/{controller}/{employeeId}",
defaults: new { id = RouteParameter.Optional }
);
that means you need a controller with following method
public class EmployeesController : ApiController
{
public IEnumerable<Employee> Get(int employeeId){...}
}
keep in mind that, unless you use action, on your controller you can only one method per http verb.
In other word your sample having 2 method for the verb get won't work unless you use explicit action for both of them.
Have you looked at using Attribute routing? We use Attribute routing extensively now, to the point that we have completely got rid of the default /controller/action type routes that we had with MapHttpRoute.
Instead we decorate our controllers as follows. Firstly we create a route prefix for the controller so that we know what the base route is that we require
/// <summary> A controller for handling products. </summary>
[RoutePrefix("api/purchasing/locations/{locationid}/products")]
public class ProductsController : PurchasingController
{
Then for each action in the controller we decorate it as follows:
[Route("", Name = "GetAllProducts")]
public IHttpActionResult GetAllProducts(int locationid, ODataQueryOptions<FourthPurchasingAPI.Models.Product> queryOptions)
{
IList<Product> products = this.GetProducts(locationid);
and
[Route("{productid}", Name = "GetProductById")]
public IHttpActionResult GetProduct(int locationid, string productid)
{
Product product = this.GetProductByID(locationid, productid);
so a call to api/purchasing/locations/1/products/ will resolve to the route named "GetAllProducts"
and a call to api/purchasing/locations/1/products/1 will resolve to the route named "GetProductById"
You could then create another GetProduct action in your controller with the same signature, just set the attribute route appropriately, e.g.
[Route("/department/{departmentId}", Name = "GetAllProductsForDepartment")]
public IHttpActionResult GetAllProductsWithDeptId(int locationid, int departmentId)
{
IList<Product> products = this.GetProducts(locationid, departmentId);
Now a call to api/purchasing/locations/1/products/department/1234 will resolve to the route named "GetAllProductsForDepartment"
I know that this example is using Web Api 2, but look at this link for Attribute Routing in Web Api. It should be exactly the same instead you will be returning something other than an IHttpActionResult.