How does ASP.NET Core Web API build URLs? - c#

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

Related

ASP.NET Core routing return 404

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.

MVC WebApi advanced custom routing

I'm struggling with this for some time now. I've searched all over the internet, didn't find any solution.
I'd like to create a webapi project with somewhat custom routing. Using VS 2019, project is of type ASP.NET WebApi on .NET Core 2.2. Routing should be like this:
Basic application must reside on url similar to "https://my.server.com/myapi". URLs which will be called are in form "https://my.server.com/myapi/{InstanceName}/{CommandName}?{customParams}"
I have one controller defined in my project and I would like to redirect all requests to that controller, where instanceName could be parameter of all the methods contained in a controller, so I would get a value for that parameter. CommandName is basicly the same as "action" RouteData by MVC principles. As you can see there is no controller specified, since all is handled by one controller.
So far I've tried setup routing like this:
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "MyRoute",
template: "{instance}/{action}",
defaults: new { controller = "MyController" });
});
}
MyController.cs
[Route("/")]
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("{instance}/info")]
public JsonResult Info(string instance, InfoCommand model)
{
// Just return serialized model for now.
var result = new JsonResult(model);
return result;
}
}
But this does not work. I get 415 response from (I think) web server when I call for example
https://my.server.com/myapi/MYINSTANCE/info?param1=value1&param2=value2
While debugging from VS this URL looks like this:
https://localhost:12345/MYINSTANCE/info?param1=value1&param2=value2
but I think it shouldn't matter for routing.
In best case scenario (putting [Route("{instance}")] above controller and [HttpGet("info")] above Info method) I get 404 response, which is also what I do not want.
I've even tried creating my own ControllerFactory, but that didn't work either (changing controller inside ControllerFactory's create method and adding another parameter to RouteData).
How to setup routing like that? Is it even possible? I would still like to use all other MVC features (model binding, proper routing, auth features, etc.), it's just this routing I cannot figure it out.
Your attempt resulting a in 415 Unsupported Media Type error was your best one.
You were only missing the FromQuery as shown below.
The error indicates that the complex type InfoCommand could not be resolved.
You must specify that it must be parsed from the querystring.
Note that the route defined via MapRoute doesn't have effect, since you are using attribute-based routing; it's only one or the other.
[Route("/")]
[ApiController]
public class MyController : ControllerBase
{
[HttpGet("{instance}/info")]
public JsonResult Info(string instance, [FromQuery] InfoCommand model)
{
var result = new JsonResult(model);
return result;
}
}
public class InfoCommand
{
public InfoCommand()
{}
public string Param1 { get; set; }
public string Param2 { get; set; }
}

Getting RouteData for actions with RouteAttribute

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.

Mixing WebAPI and MVC AttributeRouting with Route Prefix?

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.

Routing Post to a non standard uri web api

I´m writing a REST web api and I need to have an endpoint like /api/users/{id}/modify or http://localhost:8080/api/users/6/modify using a POST method.
I have a UsersController class with al read/write actions but I´m only able to access the post method by accessing /api/users, not /api/users/6/modify. I need to expand the hierarchy(if that is well said).
How can I do to achieve this?
You can use the Attribute Routing of asp.net web api.
The first thing is to enable it over the HttpConfiguration, in asp.net web api template, you can see it on the WebApiConfig.cs file.
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown...
}
}
}
After that you can define a controller which should inherits from ApiController and you can use the Route attribute to define a custom route, for sample:
[RoutePrefix("api/users")]
public class UsersController : ApiController
{
[HttpPost]
[Route("{id}/modify")]
public HttpResponseMessage PostModify(int id)
{
// code ...
}
}
The RoutePrefix will define a prefix for all actions on the controller. So, to access the PostModify you should use a route like /api/users/6/modify in a post action. If you do not want it, just remove the RoutePrefix and define the complete url on the route attribute, like this: /api/users/{id}/modify.
You also can guarantee the type of the id argument defining a route like this:
[RoutePrefix("api/users")]
public class UsersController : ApiController
{
[HttpPost]
[Route("{id:int}/modify")]
public HttpResponseMessage PostModify(int id)
{
// code ...
}
}

Categories