ASP.NET Core Attribute routing - c#

I am migrating a project to asp net core, but I could not configure these routes, i am using attributes to map actions.
Code for ASP.Net WebAPI2
[Route("api/[controller]")]
public class SalesController : Controller
{
// api/sales/1 -> ok
[HttpGet]
public HttpResponseMessage Get(int id)
{
// Logic
}
// api/sales -> ok
[HttpGet]
public HttpResponseMessage Get([FromUri] PaginationHelper pagination)
{
// Logic
}
// api/sales?me -> ok
[ActionName("Get")]
public HttpResponseMessage GetMe(bool? me)
{
// Logic
}
}
Code for ASP.Net Core
In the file Startup.cs is set app.UseMvc();
[Route("api/[controller]")]
public class SalesController : Controller
{
// api/sales/1 -> ok
[HttpGet("{id}")]
public IActionResult Get(int id)
{
// Logic
}
// api/sales -> don't work
[HttpGet] // -> ???
public IActionResult Get(PaginationHelper pagination)
{
// Logic
}
// api/sales?me -> don't work
[HttpGet] // -> ???
public IActionResult GetMe(bool? me)
{
// Logic
}
}

Maybe I'm too late to this discussion, but this could be valuable to other people who end up reading this page, like I did. The problem with your API endpoints is that you configured 2 of them to handle the same route: GET /api/sales.
If you try to access that route, the framework won't be able to distinguish between Get(PaginationHelper) and GetMe(bool?). The reason why is that they both have [HttpGet] attribute, which means that both are capable of handling the route you specified in the [Route("api/[controller]")] attribute, just above your class declaration. Since those are declared as capable of handling that same route, hence your Exception (Multiple actions matched).
The solution for your problem depends on which action you want to handle that ambiguous route. Assuming that you want the route GET /api/sales to be handled by Get(PaginationHelper), you can change the GetMe(bool?) action method and its [HttpGet] attribute to something like this:
[HttpGet("me")] // GET api/sales/me
public IActionResult GetMe() {
// Logic
}

The new framework expects a more explicit indication of the intent of the endpoint.
[Route("api/[controller]")]
public class SalesController : Controller {
[HttpGet("{id:int}")] // GET api/sales/1
public IActionResult Get(int id) {
// Logic
}
[HttpGet] // GET api/sales?page=1 assuming PaginationHelper has page property
public IActionResult Get([FromQuery]PaginationHelper pagination) {
// Logic
}
[HttpGet] // GET api/sales?me=true
public IActionResult GetMe(bool? me = false) {
// Logic
}
}
Reference Asp.Net Core: Model Binding

Related

.NET 6 best way to differentiate a route in the same controller class

I have an API controller say TestController.
I have the following route definition: api/[controller] on class level and I can call the default get method as follows: .../api/test
I want to have another method in the same controller, I also want to call it with Get and the link should be as follows: .../api/test-abc
What is the best way to differentiate this second method within the same controller class.
For test-abc, you can configure the Route attribute as ~/api/test-abc to override the controller base route. See this link for details.
[Route("api/[controller]")]
public class TestController : ControllerBase
{
public IActionResult Get()
{
// ...
}
[HttpGet]
[Route("~/api/test-abc")]
public IActionResult GetAbc()
{
// ...
}
}
This approach changes only the URL of the "test-abc"-action; all other actions of the controller use the base URL configured on class level.
In .Net 6 by following Minimal API's
app.MapGet("api/test", () =>
{
return "";
});
app.MapGet("api/test-abc", () =>
{
return "";
});
By following Traditional Method
[Route("api")]
public class TestController : ControllerBase
{
[HttpGet("test")]
public IActionResult Get()
{
// ...
return Ok();
}
[HttpGet("test-abc")]
public IActionResult GetAbc()
{
// ...
return Ok();
}
}
You can by defining the controllers route template like as below:
[Route("api/[controller]")]
public class TestController : Controller
{
...
}
So you can define a method like:
[HttpGet]
[Route("~/api/Test/Index")]
public IActionResult Index()
{
return Foo.Bar();
}
[HttpGet]
[Route("~/api/Test/Index2")]
public IActionResult Index2()
{
return Foo.Bar2();
}
[HttpGet]
public IActionResult Index3()
{
return Foo.Bar3();
}
You can call it by ../api/test/index2 to use the method Index2 or ../api/test to use Index3.
This is for the standardized way to do, in your case you would use - instead of / at the Route("...") attribute.
If you use - you need to call ../api/test-index2
For further information take a look at documentation.

Controller route attribute only relevant for root function?

I have a question about .NET Core controller routing. Recently I discovered that the controller route attribute (which you place just above the controller) only works for the root method, or at least it seems that way.
My code:
using KrabbelMicroservice.Models;
using KrabbelMicroservice.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace KrabbelMicroservice.Controllers;
[ApiController]
[Route("/profile")] // <-- This is the controller routing attribute I am talking about
public class ProfileKrabbelController : Controller
{
private readonly IProfileKrabbelService _krabbelService;
public ProfileKrabbelController(IProfileKrabbelService krabbelService)
{
_krabbelService = krabbelService;
}
[HttpGet]
public IActionResult Index()
{
// not relevant
}
[HttpGet]
[Route("/id/{krabbelId}")]
public IActionResult GetKrabbelById(long krabbelId)
{
// not relevant
}
[HttpGet]
[Route("/pid/to/{profileId}")]
public IActionResult GetKrabbelsToProfileId(long profileId)
{
// not relevant
}
[HttpGet]
[Route("/pid/from/{profileId}")]
public IActionResult GetKrabbelsFromProfileId(long profileId)
{
// not relevant
}
[HttpGet]
[Route("/pid/with/{profileId}")]
public IActionResult GetKrabbelsWithProfileId(long profileId)
{
// not relevant
}
[HttpPost]
[Route("/new")]
public IActionResult AddKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
[HttpPut]
[Route("/update")]
public IActionResult UpdateKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
[HttpDelete]
[Route("/delete")]
public IActionResult DeleteKrabbel(ProfileKrabbel krabbel)
{
// not relevant
}
}
In my swagger launch the requests look like this:
I expected that all paths would be prefixed by /profile/ but it seems like only the root function (which did not have its own route attribute) implemented the prefix.
I am not only trying to get a fix for this, but also looking for an explanation as to why my controller route attribute is ignored for the other requests. The only possibility I can think of is the specific route attributes for each endpoint overriding the controller route attribute but I would like to hear it from an expert.
Secondly I would of course also like to find a solution to this problem, preferrably not adding /profile before every seperate route but if that is the only solution so be it.
Thanks in advance!
you should be remove "/" if you have root route
ex:
[Route("test")]
[ApiController]
public class TestController3 : Controller
{
[HttpGet]
[Route("testobj")]
public TestObj Test()
{
return "test";
}
}
the even shorter in httpget
[HttpGet("testobj")]
the both output:
test/testobj

Non-attribute routes in ASP.Net Core WebApi

I need to build project, that implement REST API predefined by vendor application(which will consume it) - there is about thousand of REST-resources with some actions defined by different HTTP-Verb's(POST, GET, PUT, DELETE, etc..).
So, ideally, for each resource i should have single class like this:
public class SomethingController
{
public Something Post(string name, DateTime time)
{
// ...
}
public int PostStrange(string text)
{
// ...
}
public Something Put([FromBody]Something item)
{
// ...
}
public void Delete(int id)
{
// ...
}
}
In previous versions i can just call MapHttpRoute while registering routes, inherit classes like this from ApiController - and ASP.NET Web Api will do as i need... But in .NET Core i can't find anything like MapHttpRoute/ApiController.. Now there is routing and http-verb attributes, and i need to define everything explicitly for each class/method:
[Route("api/[controller]")]
public class SomethingController : Controller
{
[HttpPost]
public Something Post(string name, DateTime time)
{
// ...
}
[HttpPost("api/[controller]/strange")]
public int PostStrange(string text)
{
// ...
}
[HttpPut]
public Something Put([FromBody]Something item)
{
// ...
}
[HttpDelete]
public void Delete(int id)
{
// ...
}
}
Writing this attributes for each of thousands REST-resources is very boring and error prone...
Do i miss something here? Why in pretty new and modern ASP.NET Core that very common and important thing as building REST-Api made so over-complicated, compared to old ASP.NET?
There is nuget package Microsoft.AspNetCore.Mvc.WebApiCompatShim which main goal is to make migration from web api to core easier. It also provides a way to perform convention-based routing to actions you need. So, first install that package, then in startup:
public void ConfigureServices(IServiceCollection services) {
// add conventions here
services.AddMvc().AddWebApiConventions();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
app.UseMvc(routes => {
// map one global route
routes.MapWebApiRoute("WebApi", "api/{controller}");
});
}
After this small configuration you can inherit your controllers either from ApiController, which is added in package above for convenience of migration from web api, or native asp.net core Controller. Example of ApiController:
public class SomeController : ApiController {
// maps to GET /api/Some
// note - no routing attributes anywhere
public HttpResponseMessage Get() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
// maps to POST /api/Some
public HttpResponseMessage Post() {
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Native asp.net core controller:
// mark with these attributes for it to work
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class TestController : Controller {
// maps to GET /api/Test
// no routing attributes, but two "conventions" attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
You can also mark your base controller with these attributes:
[UseWebApiRoutes]
[UseWebApiActionConventions]
public class BaseController : Controller {
}
public class TestController : BaseController {
// maps to GET /api/Test
// no attributes
public IActionResult Get(string p) {
return new ObjectResult(new { Test = p });
}
}
If you are not migrating from web api - I'd suggest to use native Controller. ApiController has different structure (similar to asp.net web api ApiController), so there is not much reason to use it for anything other than its intended goal (migration from web api).
MapRoute is still there https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing
Attribute routing compliments MapRoute, not replaces it.
Apparently there are quite a few examples which drop the piece about Routing in order to simplify example. So just dig dipper.

Creating a different route to a specific action

I am working on an asp.net 5 mvc api, and I am currently working on the Accounts Controller.
since I saw in many different places that there is a convention of using /api/Tokenrouting to a login in a web api. I would like to route to that specific method without the accounts prefix, I would prefer not using a different controller, and I would prefer using Attributes over routing in Startup.cs to avoid confusion in the future.
this is what I have currently
[Route("api/[controller]")]
public class AccountsController : Controller
{
[HttpPost("login")]
public async Task<JwtToken> Token([FromBody]Credentials credentials)
{
...
}
[HttpPost]
public async Task CreateUser([FromBody] userDto)
{
...
}
}
With attribute routing you can use a tilde (~) on the Action's route attribute to override the default route of the Controller if needed:
[Route("api/[controller]")]
public class AccountsController : Controller {
[HttpPost]
[Route("~/api/token")] //routes to `/api/token`
public async Task<JwtToken> Token([FromBody]Credentials credentials) {
...
}
[HttpPost]
[Route("users")] // routes to `/api/accounts/users`
public async Task CreateUser([FromBody] userDto) {
...
}
}
For ASP.NET Core it seems that the tilde ~ symbol (see accepted answer) is not needed anymore to override the controller's route prefix – instead, the following rule applies:
Route templates applied to an action that begin with a / don't get combined with route templates applied to the controller. This example matches a set of URL paths similar to the default route.
Here is an example:
[Route("foo")]
public class FooController : Controller
{
[Route("bar")] // combined with "foo" to map to route "/foo/bar"
public IActionResult Bar()
{
// ...
}
[Route("/hello/world")] // not combined; maps to route "/hello/world"
public IActionResult HelloWorld()
{
}
}
from https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
In the preceding code, the Index method templates must prepend / or ~/ to the route templates. Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller.

Web Api 2 API not recognizing Multiple Attributes for Routing (Versioning)

I'm trying to implement both Attribute Routing and the VersionedRoute from RoutingConstaints Sample but when I use both on a controller, the versioned attribute no longer works.
What would I need to modify on the attribute to get it to play nice with Attribute Routing?
For code example download the sample project (or just look at the few files from the above link) and then modify the routes as such:
// When I use the RoutePrefix, VersionedRoute no longer works (Sending "Api-Version" through http header doesn't route correctly
// If I remove the RoutePrefix I can use VersionedRoute again
// What do I need to change in its code to be able to use both?
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[VersionedRoute("api/Customer", 1)] // I'd rather not have to use this here at all and just use a single one on the class, but having both nor just one on either works right now.
[Route("")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
VersionedRoute Code
VersionConstraint Code
Edit: Please let me know if you need more information or even post ideas or things to try :)
Edit2: Here is an example of what I'm trying to do from Troy Hunt's Blog: http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html
Edit3: Here is what I'd like to code to be as close to since it would reduce a lot of the overhead and magic strings.
[VersionedRoute("api/Customers", 1)] // This route would be used as http://url/api/customers with a header of "api-version: 1"
[RoutePrefix("api/v1/Customers")] // This route would be used purely through url versioning of http://url/api/v1/Customers
public class CustomersV1Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customers);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customer);
}
}
[VersionedRoute("api/Customers", 2)] // This route would be used as http://url/api/customers with a header of "api-version: 2"
[RoutePrefix("api/v2/Customers")] // This route would be used purely through url versioning of http://url/api/v2/Customers
public class CustomersV2Controller : ApiController {
/* Other stuff removed */
[Route("")]
public IHttpActionResult Get()
{
// Removed
return Ok(customersThatAreDifferentThanV1);
}
[Route("{id:int}")]
public IHttpActionResult GetById(int id)
{
// Removed
return Ok(customerThatIsDifferent);
}
}
Edit: Last bump, trying to only have to write the route version information once per route, at the controller attribute level and not per-action.
The Route and VersionedRoute attributes are working fine together, but your RoutePrefix attribute is also applied to your VersionedRoute (try accessing /api/v1/Customers/api/Customer - you'll get a response when the api-version header is set)
The following code would produce the desired behaviour with regards to the two URLs in your example returning the correct responses, but obviously this does not solve your problem of wanting one VersionedRoute and one RoutePrefix at the top of the class. Another approach would be needed for this. You can, however, have separate controllers for different api versions.
[RoutePrefix("api")]
public class CustomersV1Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 1)]
[Route("v1/Customers")]
public IHttpActionResult Get()
{
return Json(_customers);
}
}
An improvement would be to create your own attribute instead of Route so you wouldn't need to prefix the version every time:
public class CustomVersionedRoute : Attribute, IHttpRouteInfoProvider
{
private readonly string _template;
public CustomVersionedRoute(string route, int version)
{
_template = string.Format("v{0}/{1}", version, route);
}
public string Name { get { return _template; } }
public string Template { get { return _template ; } }
public int Order { get; set; }
}
[RoutePrefix("api")]
public class CustomersV2Controller : ApiController
{
/* Other stuff removed */
[VersionedRoute("Customers", 2)]
[CustomVersionedRoute("Customers", 2)]
public IHttpActionResult Get()
{
return Json(_customers);
}
}

Categories

Resources