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);
}
}
Related
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
Is there a way to automatically add a suffix on all endpoint routes e.g .json.
v1/users.json
v1/users/{id}.json
so what I have tried so far is I created a BaseController which look like this
[ApiController]
[Route("v1/[controller].json")]
public class BaseController : ControllerBase
{
}
but every time I use it to my controller it looks like this
v1/users.json
v1/users.json/{id}
You can add extra routing to the actual endpoints rather than the controllers
[ApiController]
[Route("v1/[controller]")]
public class BaseController : ControllerBase
{
[HttpGet(".json")]
public IActionResult Get()
{
}
// Without Route Parameters
[HttpGet("{id}.json")]
public IActionResult Get([FromRoute] int id)
{
...
}
// With Route and Query Parameters
[HttpGet("{id}.json/friend")]
public IActionResult Get([FromRoute]int id,[FromQuery] string friendName)
{
...
}
// With Route and Query Parameters and Body
[HttpPost("{id}.json/friends")]
public IActionResult Get([FromRoute]int id,[FromQuery] string message, [FromBody]IFilter filter)
{
...
}
}
You could use the URL Rewriting Middleware to accept URLs with .json and then simply remove it. So something like:
/api/users/123/picture.json?query=123
would become:
/api/users/123/picture?query=123
You can do this by adding the following code to your Startup's Configure method:
var rewriteOptions = new RewriteOptions()
.AddRewrite(#"^(.*?)(?:\.json)(\?.*)?$", "$1$2");
app.UseRewriter(rewriteOptions);
See the docs for more information.
Caveat: If you use Url.Action(...), etc. to generate a URL within code, then it won't include .json. The rewrite only affects incoming requests.
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.
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
I have a case where I'm trying to create a whole bunch of Api controllers. I have created a base class controller that has all the basic GET, PUT, POST, DELETE actions as well as a few others that seem common for the project.
I then use T4 to script out each table in my db (the EF context, but none the less) as a controller, obviously inheriting from that parent controller (who ultimate inherits from ApiController. It all works great except for some routing issues.
Apparently you cannot define attributed routes in the base class and have the MapHttpAttributeRoutes find them.
Given this suedo code below is there any suggestion contrary to this belief and is there a "good" way to handle this? For now the thought is to ditch the base class and script each controller out in T4.
public class TestBaseController: ApiController {
// GET api/user
[Route("")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
[RoutePrefix("apiv2/user")]
public class UserController: TestBaseController
{
[Route("type")]
[HttpGet]
public string GetType()
{
return "my test type";
} ...
apiv2/user/ results in a 404, but apiv2/user/type returns the expected string.
By default attribute routes are not inherited, however with Web Api 2.2, we added an ability to do that:
Example:
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);
}
}