I have a simple ASP.NET Core API with the following controller:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
public IActionResult Index()
{
return Ok("index");
}
public IActionResult Get()
{
return Ok("get");
}
}
When I hit URL:
"http://localhost:6001/api/home"
With a GET request, I get the following error:
"The request matched multiple endpoints."
Normally, if I don't specify an action name, shouldn't it call the Index() method?
In ASP.NET Core API, action methods are matched by a combination of HTTP verb used as attribute on the method (which is HttpGet by default if you don't add any) and the route parameter used with it. In that sense both your action methods looks similar to the routing system. It sees two HttpGet method with no route parameter. That's why both method matches the request -
"http://localhost:6001/api/home"
making the routing system confused which to select. That's exactly what the error message is saying.
You need to make your non-default action method more specific, so that the routing mechanism can differentiate between the two. Something like -
[HttpGet("data")]
public IActionResult Get()
{
return Ok("get");
}
Now your Index method will be matched as default action method with the above URL.
Both of your endpoints are identical. Use routes to differentiate them. For example:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index()
{
return Ok("index");
}
[HttpGet, Route("data")]
public IActionResult Get()
{
return Ok("get");
}
}
This would open up:
http://localhost:6001/api/home/
and
http://localhost:6001/api/home/data
respectively using GET
You could also change the verb, for example use HttpGet for one and HttpPost for another.
Related
In my MVC controller, i have two action methods which are rendering View, other 6 Action Methods are either HttpGet or HttpPost. I want to do the below
for ActionMethods rendering View it will be "controller/action". But for the GET/POST, i want it to be api/whatevernameilike.
Is it acheivable in asp.net mvc core?
TIA
Worth trying as well if the previous methods aren't working:
[HttpGet("/api/whatevernameilike")]
Attribute Routing in ASP.NET Web API 2 has an example for this:
Use a tilde (~) on the method attribute to override the route prefix:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET /api/authors/1/books
[Route("~/api/authors/{authorId:int}/books")]
public IEnumerable<Book> GetByAuthor(int authorId) { ... }
// ...
}
Routing to controller actions in ASP.NET Core shows the following:
[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.
You can use route attribute on top of your controller
ex:
[Route("Api/Yourchosenname")]
public async Task<IActionResult> Action()
{
return Ok();
}
I am creating a web api using ASP.NET Core 3.1 and am trying to route URL to controllers. So far I have a basic controller like this:
[Route("abc")]
[ApiController]
public class ABCController : ControllerBase
{
// GET: abc/1234
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetABCService(long id)
{
...
}
}
Which correctly route me to the page when I type in http://myurl/abc/1234. The next thing I controller I wanted to wire is like this:
[Route("xxx")]
[ApiController]
public class XXXController : ControllerBase
{
// GET: abc/1234/XXX
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetXXXService(long id)
{
...
}
}
Somehow it keeps giving me 404 when I type in http://myurl/abc/1234/xxx. I made the first one works by setting my endpoint like this :
app.UseEndpoints(endpoints =>{
endpoints.MapControllerRoute(
"abc",
"abc/{id}",
new {controller = "ABCController", action = "GetABCService"});
//My current endpoint mapping for the second controller:
endpoints.MapControllerRoute(
"xxx",
"abc/{id}/xxx",
new {controller = "XXXController", action = "GetXXXStatus" });
}
I could not figure out why I would get 404 with http://myurl/abc/1234/xxx. Any insight?
You want to say XXXController to route 'abc' first by [Route("abc")]
[Route("abc")]
[ApiController]
public class XXXController : ControllerBase
{
[HttpGet("{id}/xxx")]
public ActionResult<string> GetXXXService(long id)
{
return "ActionResult";
}
}
When you use attribute routing, e.g. with [Route] or [HttpGet(…)], then convention-based routing is ignored. So the route templates you define with MapControllerRoute are not taken into account when generating the routes for your API controller. In addition, using the [ApiController] attribute actually enables certain API-related conventions. And one of those convention is that you may only use attribute routing for your API controllers.
So if you only have API controllers in your project, then you can leave out the MapControllerRoute calls. Instead, you will have to make sure that your attribute routing is correct.
In your case, if you want the route abc/1234/XXX to work, then you will have to use a route of abc/{id}/XXX.
I have an HTTP-GET method looks like below one
[Route("api/[controller]")]
[ApiController]
public class CityController : ControllerBase
{
public ActionResult Get(int id)
{
try
{
var city = new { CityName = "Gotham" };
return Ok(city);
}
catch(Exception ex)
{
return StatusCode(500);
}
}
}
For both type of the requests
Request:
GET http://localhost:49915/api/city
POST http://localhost:49915/api/city
Response:
status: 200 OK
-------------------
{
"cityName": "Gotham"
}
Now my questions around it are,
As it is a GET, should it supposed to accept a POST?
Shouldn't it return a 405 status code and Why it does not? (at least I'm expecting)
In such case, if I have to return 405 what to be done?
As it is a GET, should it supposed to accept a POST?
While you assume it is a get because of the action name and convention-based routing, you would be mistaken as the controller has been decorated for attribute routing.
[Route("api/[controller]")]
and thus ignores convention based routes if a match is made. Note that PUT and DELETE
PUT http://localhost:49915/api/city
DELETE http://localhost:49915/api/city
should also work on the same action.
Shouldn't it return a 405 status code and Why it does not? (at least I'm expecting)
The action matches both calls by design since no directive was given for the action.
[Route("api/[controller]")]
[ApiController]
public class CityController : ControllerBase {
// GET api/city?id=2 //Note id would be optional as a query variable.
[HttpGet]
public ActionResult Get(int id) {
try {
var city = new { CityName = "Gotham" };
return Ok(city);
} catch(Exception ex) {
return StatusCode(500);
}
}
}
Now with the HttpGet in place, if
POST http://localhost:49915/api/city
or another HTTP method is done, you will get the 405 error because the path matches but the method does not.
In such case, if I have to return 405 what to be done?
with the attribute route in place, the framework will do it for you so there is nothing more for you to do.
Reference Routing to controller actions in ASP.NET Core
Mixed routing: Attribute routing vs conventional routing
What distinguishes the two types of routing systems is the process applied after a URL matches a route template. In conventional routing, the route values from the match are used to choose the action and controller from a lookup table of all conventional routed actions. In attribute routing, each template is already associated with an action, and no further lookup is needed.
I have a custom IRouter implementation, which looks in its basic form like this, for simplicity's sake I hardcoded some values:
public class MyRouter : IRouter
{
private readonly IRouter router;
public MyRouter (IRouter router)
{
this.router = router;
}
public async Task RouteAsync(RouteContext context)
{
context.RouteData.Values["controller"] = "Home";
context.RouteData.Values["action"] = "Index";
context.RouteData.Values["area"] = "";
await router.RouteAsync(context);
}
}
This works for a simple controller without a [Route] attribute defined:
public class HomeControlller
{
public IActionResult Index()
{
return View();
}
}
Again, this works correctly. Going to / will show the page.
However, as soon as I add [Route] attributes, I get a 404:
[Route("foo")]
public class HomeControlller
{
[Route("bar")]
public IActionResult Index()
{
return View();
}
}
Now, if I go to /foo/bar, I will see the page. However, if I go to /, I get a 404.
How can I fix this? If I look at the RouteData values when going to /foo/bar, I still see the values Home and Index as values for controller and action, respectively.
This is by design.
Actions are either conventionally routed or attribute routed. Placing a route on the controller or the action makes it attribute routed. Actions that define attribute routes cannot be reached through the conventional routes and vice-versa. Any route attribute on the controller makes all actions in the controller attribute routed.
Reference: Mixed routing: Attribute routing vs conventional routing.
For Conventional router, it is using MvcRouteHandler, and Attribute route will use MvcAttributeRouteHandler. When Controller or Action used with Route[], it will not go to Converntional router when you request the specific method.
I have an action which names "Delete" in an ASP.NET WebApi controller, and I want to request it by HttpPost Method and I don't want to change its name.Even through I put the HttpPost Attribute on it, it also doesn't work and returns "405 method not allowed".So what can I do next?
By the way, it is strange that some other actions like this work well.
using System.Web.Mvc;
namespace ECIS.Mobile.OpenApi.WebApi.Controllers.Manager
{
public class AdController : System.Web.Http.ApiController
{
[HttpPost]
public int Delete(int id)
{
return 1;
}
}
}