I've specified my routing like this:
[RoutePrefix("users")]
public class UsersController : ApiController
{
[ResponseType(typeof(List<User>))]
[Route("")]
public IHttpActionResult GetAll()
{
}
[Route("{birthdate}")]
[ResponseType(typeof(List<User>))]
public IHttpActionResult GetByBirthdate(DateTime birthdate)
{
}
But when I am using this url: localhost/Users?birthdate=1907-04-19&api-version=2.0
I get redirected to GetAll() method. Why is that?
localhost/Users?birthdate=1907-04-19&api-version=2.0
that mean you call url users with param birthdate
If you want to go to second you need use
http://localhost/users/birthdate?birthdate=1907-04-19
Related
I tried to do some simple thing. I wanted to have different action name and different method name:
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
}
Look that there is no Route attribute on the controller.
In such case tag helper asp-action works perfectly. But I thought that my action dosth would be placed in: localhost/Super/dosth
But it was not. So I figured it out that I probably should set the route for the whole controller, like this:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
But now asp-action stopped working. For example:
<a asp-controller="Super" asp-action="Register">
creates anchor to: localhost/Super and not to: localhost/Super/Register
When I remove Route tag from controller it works again.
My mappings are configured as standard says:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
So, how come asp-action does not work when there is a Route attribute on the whole controller
Applying [Route] attribute on a controller enables attribute routing for all controller methods. So, by doing so you force yourself to provide the route for every method (in one way or another).
With [Route("[controller]")] the base route template for your controller actions is just the controller name, so if you have multiple actions without [Route] or [HttpGet] (and other HTTP verbs) attributes applied on them:
[Route("[controller]")]
public class SuperController: Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
...you get yourself a AmbiguousMatchException because multiple controller actions will be matching same route:
/Super
You can either explicitly specify the route for every action:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
[HttpGet("Register")]
public IActionResult Register()
{
return View();
}
}
or specify action name as an part of expected route already on controller level:
[Route("[controller]/[action]")]
public class SuperController : Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
then, you don't have to specify the routes for actions, because you applied a route template on a controller level. Your actions will inherit that route template.
But, be aware that in order to overwrite it you'll have to overwrite it all, otherwise you'll append to the route template.
[HttpGet("/[controller]/dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
Read more about routing in official documentation.
I'm working on app with recipes.
I'm wrote delete recipe method and this worked fine but then had to add method which delete photo from recipe.
Working on Core 3.1.1 with Api's
I'm getting this error when I try to have 2 "Delete" methods
My code:
namespace RecipesApp.API.Controllers
{
[Authorize]
[Route("api/users/{userId}/recipes/{recipeId}/photos")]
[Route("api/users/{userId}/recipes")]
[Route("api/[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
// http://localhost:5000/api/users/{userId}/recipes/{id}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteRecipe(int userId, int id)
{
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeletePhoto(int recipeId, int id)
{
}
The Route annotation must be unique on the class definition, and is mainly used to prefix the controller path. The Http* annotations go on the controller public methods, their value represents the unique path assigned to each one.
Combining both (Route and Http*) you will get the full template path assigned to your method. According to this, your code must look like this:
namespace RecipesApp.API.Controllers
{
[Authorize]
[Route("/api/users/{userId}/[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
// /api/users/{userId}/recipes/{id}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteRecipe(int userId, int id)
{
//
}
// /api/users/{userId}/recipes/{id}/photos
[HttpDelete("{id}/photos")]
public async Task<IActionResult> DeletePhoto(int recipeId, int id)
{
//
}
}
}
I am using .NET Core 2.2 and I have the controller below
[Route("api/[controller]")]
[ApiController]
public class CarsController : ControllerBase
{
[HttpPost]
[Route("api/cars/search")]
[ActionName("search")]
public ActionResult<IEnumerable<string>> SearchForCar([FromBody] SearchCriteria searchCriteria)
{
return new string[] { "value1", "value2" };
}
}
I am new to pure web api controllers.
I am confused about why when I post json to
http://localhost:51285/api/cars/search
I get 405 method not allowed?
I would normally have a route of
[Route("api/[controller]/action")]
That does work (once I remove route from the method attributes), but this wasn't the default provided in the template
Could someone let me know what I am missing?
Am I breaking convention by changing to
[Route("api/[controller]/[action]")]
Cheers
Paul
Since you not using the root slash in your action "/", the MVC middleware will search concatenating the route for controller and the action, should work like this
[Route("/api/[controller]/[action]")] // Check the root slash as first character
public ActionResult<IEnumerable<string>> SearchForCar([FromBody]
SearchCriteria searchCriteria)
Or like this
[Route("api/[controller]/[action]")] [ApiController] public class
CarsController : ControllerBase
Or
In controller:
[Route("api/[controller]")] [ApiController] public class
CarsController : ControllerBase
In action:
[Route("search")] // [action] Takes the method name
public <ActionResult<IEnumerable<string>>
SearchForCar([FromBody] SearchCriteria searchCriteria)
Why can't Web API Core 2 tell these apart?
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values?name=dave
[HttpGet]
public string Get(string name)
{
return $"name is {name}";
}
Here's what happens -
Both http://localhost:65528/api/values and http://localhost:65528/api/values?name=dave cause the first Get() method to execute.
This exact code works fine in Web Api 2.
I know multiple ways of getting around this, but I don't know why it happens.
Can someone explain why this has changed?
I don't think you can even compile your code in ASP.NET Core Mvc 2.0 since you have 2 actions mapped to same route [HttGet] api/values:
AmbiguousActionException: Multiple actions matched.
Remember, ASP.NET Web API uses the HTTP verb as part of the request to figure which action to call. Although it uses conventional routing (you name your actions Get, Post, Put and Delete, etc) if you don't have route attribute specify, I would highly recommend to always use routing attribute to annotate your controllers and actions.
Api Design time
Now it's up to you to design the route, as a developer. Remember the route is supposed to be a Uri that can identify a resource / resources.
Use the name as identifier along with the route
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")] // constraint as a string
public IActionResult GetByName(string name)
{
...
}
}
Use the name as filter, against the resource collection
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
// api/customers?name=dave
[HttpGet]
public IActionResult Get(string name)
{
...
}
}
To confuse you more
api/customers/dave will still execute GetById first!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
...
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
...
}
}
Both methods GetByName and GetById are potential candidates but MVC picks GetById method first because MVC compares the method/template name {name} and {id} through case-insensitive string comparison, and i comes before n.
That's when you want to put constraints.
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
You can also specify the Ordering too!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/portland
[HttpGet("{city:alpha}", Order = 2)]
public IActionResult GetByCity(string city)
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}", Order = 1)]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
Without the Order, the method GetByCity will be in favor than GetByName because character c of {city} comes before the character n of {name}. But if you specify the order, MVC will pick the action based on the Order.
Sigh the post is too long....
Because in your case the best match in the route pipeline is the default httpget attribute (the one that get all). The query is a regular string so if you don't ask what you want from the query the best match is still the one that get all.
[HttpGet]
public string Get([FromQuery]string name)
{
return $"name is {name}";
}
The [FromQuery] is pointing to the key "name" in the query string to get the value.
Your should read Routing in asp.net core
I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?
Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22
Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)