I know this has been asked a lot of times but nobody really wraps it up.
I'm currently using a .NET Core Backend which has a web api controller.
I know that there are multiple ways of handling the routing in .NET Core e.g.
Using Constraints
[HttpGet("{id:int}")]
public string GetById(int id)
{
return "item " + id;
}
Creating Default Routes
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Building RESTful Routes
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"hello", "world!"};
}
// POST api/values
[HttpPost]
public void PostCreate([FromBody] string value)
{
}
}
and a couple more options.
However I just can't figure out how to pass multiple parameters in one route.
In my case I have to update two different objects at the same time and have to use either an HTTP Patch or HTTP Put request.
I could go with something like api/ExampleName/Id but what I need is something like api/ExampleName/ObjectOneID&ObjectTwoID
Does anyone know how to build something like this?
I thought about creating a default route in the startup file and configure it right there. However I suppose it wants a specific syntax in order to read multiple parameters
You can use parameters in the URI's themselves - like http://myapi.com/api/{controller}/{action}?id=someId&anotherId=someOtherId
Your action method would then be like
public void PostCreate([FromUri]Guid someId, [FromUri]Guid anotherId)
[HttpPatch]
public async Task<string> Patch([FromBody] Model request)
where Model is
class Model
{
public int ObjectOneID {get; set;}
public int ObjectTwoID {get; set;}
}
to use it just create request with body that contains ObjectOneID and ObjectTwoID like
PATCH api/ExampleName
Content-Type: application/json
{
"ObjectOneID":1,
"ObjectTwoID":2
}
In this scenario I suggest to use POST/PUT with model
[Route("dummy")]
[HttpPost]
public async Task<IHttpActionResult> Dummy(Foo req)
{
//logic
}
public class Foo
{
public CustomObjectBody ObjectOneID {get;set;}
public CustomObjectBody ObjectTwoID {get;set;}
}
public class CustomObjectBody
{
public int Property1 {get;set;}
public int Property2 {get;set;}
}
Related
Good afternoon,
I'm having some trouble with endpoint routing in my web API using attribute routing and the ASP.NET core routing middleware.
I have an API controller that looks roughly like the following:
public class UsersController : ControllerBase
{
[HttpGet]
[Route("v1/users/{id}", Name = nameof(GetUser), Order = 1)]
public async Task<ActionResult> GetUser([FromQuery(Name = "id")] string userGuid)
{
// Implementation omitted.
}
[HttpGet]
[Route("v1/users/me", Name = nameof(GetCurrentUser), Order = 0)]
public async Task<ActionResult> GetCurrentUser()
{
// Implementation omitted.
}
}
I am trying to configure the endpoint routing so that requests for 'v1/users/me' are routed to the 'GetCurrentUser()' method, while requests matching the template 'v1/users/{id}' (where {id} != me) are routed to the 'GetUser()' method. I was hoping that this could be solved by placing the 'v1/users/me' endpoint before the other endpoint in the endpoint order, but it seems the order parameter isn't respected by the routing middleware. I've also tried explicitly mapping the 'v1/users/me' endpoint before mapping the remaining endpoints, but this doesn't seem to work either.
Here is the current startup configuration:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseResponseCompression();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// Explicit mapping of the GetCurrentUser endpoint - doesn't seem to do anything.
// endpoints.MapControllerRoute("v1/users/me", "Users/GetCurrentUser");
endpoints.MapControllers();
}
}
Is this possible to achieve with attribute routing, and if so, what am I missing?
Thanks!
This should already work fine if you just leave the defaults like this:
[HttpGet("v1/users/{id}")]
public async Task<ActionResult> GetUser(string id)
{
return Ok(new { id = id });
}
[HttpGet("v1/users/me")]
public async Task<ActionResult> GetCurrentUser()
{
return Ok(new { id = "current" });
}
With attribute routing, routes that contain constant parts are favored over routes that contain a route variable at the same location. So v1/users/me is ranked higher than v1/users/{id} with id = "me", so you should see the GetCurrentUser run when you access that route. This is regardless of method ordering within your controller.
The issue was with the annotation of the API endpoint methods.
I mistakenly marked the parameter in the GetUser(string id) endpoint with the [FromQuery] attribute rather than the [FromRoute].
The following works as expected:
public class UsersController : ControllerBase
{
[HttpGet]
[Route("v1/users/{id}", Name = nameof(GetUser))]
public async Task<ActionResult> GetUser([FromRoute(Name = "id")] string userGuid)
{
// Changed from [FromQuery(Name = "id")] to [FromRoute(Name = "id")]
// Implementation omitted.
}
[HttpGet]
[Route("v1/users/me", Name = nameof(GetCurrentUser))]
public async Task<ActionResult> GetCurrentUser()
{
// Implementation omitted.
}
}
I have created a new Web API project from the Visual Studio Templates and then I have followed the following tutorial for adding OData to this project.
https://devblogs.microsoft.com/odata/supercharging-asp-net-core-api-with-odata/
Calling
https://localhost:xxx/api/Assets
and
https://localhost:xxx/api/Assets/1
return all Assets, while the latter should return only 1 asset (where id = 1)
My code:
public class AssetsController : ControllerBase
{
private IAssetService _service;
private IMapper _mapper;
public AssetsController (IAssetService _service, IMapper mapper)
{
this._service = _service;
this._mapper = mapper;
}
[HttpGet]
[EnableQuery()]
public ActionResult<IEnumerable<Asset>> Get()
{
return this._service.GetAllAssets().ToList();
}
[HttpGet("{id}")]
[EnableQuery()]
public Asset Get(int id)
{
return _service.GetById(id);
}
}
I have debugged to verify that the Get(int id) function is never called.
I have tried defining my route explicitly like this:
[HttpGet]
[Route("GetById/{id}")]
[EnableQuery()]
public Asset Get(int id)
{
return _service.GetById(id);
}
EDIT
Routing defined in startup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
/* snip */
app.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
routeBuilder.Select().Filter().OrderBy().Expand().Count().MaxTop(10);
routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
});
}
This makes no difference.
Any ideas?
There're two approaches to solve this question.
Approach 1 : rename the id parameter to key
According to the OData v4 Web API docs :
Here are some rules for the method signatures:
If the path contains a key, the action should have a parameter named key.
If the path contains a key into a navigation property, the action should have a > parameter named relatedKey.
POST and PUT requests take a parameter of the entity type.
PATCH requests take a parameter of type Delta, where T is the entity type.
We should have a parameter named key:
[HttpGet("{id}")] // actually, this line doesn't matter if you're using OData, but it's better to rename it to `key` too
[EnableQuery()]
public IActionResult Get(int key)
{
....
}
Approach 2: rename the Get method to GetAsset
According to OData v4 Web API docs:
When Web API gets an OData request, it maps the request to a controller name and an action name. The mapping is based on the HTTP method and the URI. For example, GET /odata/Products(1) maps to ProductsController.GetProduct.
We could also rename the action method to GetAsset as below:
[HttpGet("{id}")]
[EnableQuery()]
public IActionResult GetAsset(int id)
{
...
}
this worked me...
[HttpGet]
public ActionResult<IEnumerable<Asset>> Get()
{
return this._service.GetAllAssets().ToList();
}
[HttpGet("{id}")]
public Asset Get(int id)
{
return _service.GetById(id);
}
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 have a weird situation... I have this api methods:
[Route("api/something")]
public class MyController : Controller
{
[HttpGet("test-{id}")]
public Task<T> method1() { ... }
[HttpGet("test-something-{id}")]
public Task<T> method2() { ... }
}
I want to call api/something/test-something-1 but my api calls api/something/test-1
why?
The route "test-{id}" matches api/something/test-something-1 where template parameter would end up as id = something-1.
That is the reason why when you call api/something/test-something-1 that it calls method1 with route template test-{id}
When there are route conflicts like this then you should use route constraints to better differentiate routes.
[Route("api/something")]
public class MyController : Controller {
[HttpGet("test-{id:int}")]//Matches GET api/something/test-1
public Task<IActionResult> method1(int id) {
//...
}
[HttpGet("test-something-{id}")]//Matches GET api/something/test-something-any_id_here
public Task<IActionResult> method2(string id) {
//...
}
}
You can apply route constraints to the second one as well if the id is suppose to be an int as well.
[HttpGet("test-something-{id:int}")]//Matches GET api/something/test-something-1
public Task<IActionResult> method2(int id) {
//...
}
Reference: Routing in ASP.NET Core : Route Constraint Reference
Class declaration is incorrect
[RoutePrifix("api/something")]
public Class blabla : Controller
{
[HttpGet("test-{id}")]
public Task<T> method1{}
[HttpGet("test-something-{id}")]
public Task<T> method1{}
}
In route config file you have to enable attribute routing.
routes.MapMvcAttributeRoutes();
I've recently asked a few questions about the best way to create a web api which utilises the same url as my main mvc site. I deduced the best way was to make the necessary changes to my MVC site to include web api and the necessary routing.
I have mainly followed How to add Web API to an existing ASP.NET MVC 4 Web Application project? but I have run into problems. The code compiles fine and it is clearly looking for the route but I get the error:
No HTTP resource was found that matches the request URI 'http://localhost:2242/api/value'.
No type was found that matches the controller named 'value'.
My WebApiConfig:
class WebApiConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
}
}
my global.asax:
public class MvcApplication : 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);
Database.SetInitializer<ApplicationDbContext>(null);
}
}
my api controller:
public class ValuesController1 : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
Other posts have corroborated that this is a correct and working setup...I created a separate webapi project to compare and this is all correct routing wise apparently. It would be far preferable to build this into my MVC website, does anyone have any ideas? This poster No type was found that matches controller had the same problem and the solution he found was to copy everything into a new project....that really isn't something I want to do/see why I should need to do.
I think it is because of your Controller's name : ValuesController1
A controller has to be suffixed by "Controller", the 1 may be the cause of your issue.
The name of the controller ValuesController1 doesn't match convention - in order for the default route to match /api/value based on the default convention set in your call to configuration.Routes.MapHttpRoute(...), the controller should be called ValueController:
public class ValueController : ApiController
{
public IEnumerable<string> Get()
// ...
However, if you intend to deviate from the configured convention, you can apply RouteAttribute and RoutePrefixAttribute in conjunction with the Http* verb attributes to customise controller and method routes, e.g.
[RoutePrefix("api/Foo")]
public class ValuesController : ApiController
{
// get api/Foo/value
[HttpGet]
[Route("value")]
public IEnumerable<string> NameDoesntMatter()
{
return new string[] { "value1", "value2" };
}
// get api/Foo/value/123
[HttpGet]
[Route("value/{id}")]
public string AnotherRandomName(int id)
{
return "value";
}
Before using the RouteAttribute you will need to add the following to your WebApiConfig.Register(HttpConfiguration config):
config.MapHttpAttributeRoutes();
Even with the routing attributes, note however that the controller class name still needs to end with the suffix Controller, i.e. cannot end in the suffix 1. It is surprisingly difficult to alter this convention.