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.
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 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.
I have the following code:
[Produces("application/json")]
[Route("api/[controller]")]
public class MarketReportInstancesTableController : BaseController
{
internal readonly MyIRIntegrationDbContext Context;
public MarketReportInstancesTableController(ILogger<MarketReportInstancesTableController> logger,
MyIRIntegrationDbContext context) : base(logger)
{
Context = context;
}
[HttpGet (Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request)
{
.... methd body in here
}
And I try to access with a URL like:
http://somehost/pca/api/MarketReportInstancesTable/pagedata
Which DOES NOT work, but
http://somehost/pca/api/MarketReportInstancesTable/
DOES WORK.
My question would be, why does the route do that? I want to have many paths in the same WebAPI controller.
Am I approaching it wrong?
You have no route template in the route. You only have a route name
Route names can be used to generate a URL based on a specific route. Route names have no impact on the URL matching behavior of routing and are only used for URL generation. Route names must be unique application-wide.
emphasis mine
//GET api/MarketReportInstancesTable/pagedata
[HttpGet ("pagedata", Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request) {
//.... methd body in here
}
Using [HttpGet] without a route template is the same as [HttpGet("")] which will map to the root of the controller with route prefix.
This explains why your root call works.
Reference Routing in ASP.NET Core
Reference Routing to Controller Actions
I'm pretty new to setting up routes and routing in MVC. At my last job we used attribute routing for our WebAPI stuff, so I'm pretty familiar with that (RoutePrefix, Route, and HttpGet/HttpPost attributes, etc). And I can get my current project to work just fine with attributes.
So now what I want to do is "prefix" all of the webApi routes with "api". So instead of going to mysite/test/hello, I want to go to mysite/api/test/hello.
This is what I have, using only attribute routing, and it works just fine:
[RoutePrefix("Test")]
public class TestController : ApiController
{ ....
[HttpPost]
[Route("{message}")]
public HttpResponse EchoBack(string message)
{
// return message ... in this case, "hello"
}
}
Now, I know I can change the RoutePrefix to "api/Test" (which works 100%), but I don't want to change all my controllers, I would rather be able to perform this by changing the values passed in to config.Routes.MapHttpRoute in WebApiConfig.
Is this possible?
What you describe can be done in attribute routing by using what is referred to as a global route prefix.
Referencing this article Global route prefixes with attribute routing in ASP.NET Web API
Create a DirectRouteProvider
public class CentralizedPrefixProvider : DefaultDirectRouteProvider {
private readonly string _centralizedPrefix;
public CentralizedPrefixProvider(string centralizedPrefix) {
_centralizedPrefix = centralizedPrefix;
}
protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor) {
var existingPrefix = base.GetRoutePrefix(controllerDescriptor);
if (existingPrefix == null) return _centralizedPrefix;
return string.Format("{0}/{1}", _centralizedPrefix, existingPrefix);
}
}
To quote article.
The CentralizedPrefixProvider shown above, takes a prefix that is
globally prepended to every route. If a particular controller has it’s
own route prefix (obtained via the base.GetRoutePrefix method
invocation), then the centralized prefix is simply prepended to that
one too.
Configure it in the WebAPiConfig like this
config.MapHttpAttributeRoutes(new CentralizedPrefixProvider("api"));
So now for example if you have a controller like this
[RoutePrefix("Test")]
public class TestController : ApiController {
[HttpPost]
[Route("{message}")]
public IHttpActionResult EchoBack(string message) { ... }
}
The above action will be accessed via
<<host>>/api/Test/{message}
where api will be prepended to the controller actions route.
I'm using attribute routing in my MVC5 application and it is working fine. When I tried to create an area and placed attribute routing on the controller inside it, it returns 404.
I know, to enable attribute routing inside Area, I have to use [RouteArea("Area Name Here")] and also have to add routes.MapMvcAttributeRoutes(); inside my RouteConfig class. I did all this and designed my controller like this:
[RouteArea("Client")]
[RoutePrefix("Client")]
public class ClientController : Controller
{
#region Properties
private readonly string apiUrl = ConfigurationManager.AppSettings["apiUrl"];
#endregion
#region Constructor
public ClientController()
{
}
#endregion
#region Action Methods
[HttpGet, Route("create")]
public ActionResult Index()
{
--logic here
}
#endregion
}
When i run the application using the route: http://localhost:26189/Client/create, I'm able to hit the constructor but not the Index method. Interestingly, if i remove the attribute [HttpGet, Route("create")] and try this route http://localhost:26189/Client/Index it will hit the Index method.
I'm going through these links but not found the exact fix:
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/#route-areas
You likely have ordering problems in your startup path. Make sure things are called in this order:
AreaRegistration.RegisterAllAreas();
routes.MapMvcAttributeRoutes();
Any convention-based routes