Mixing WebAPI and MVC AttributeRouting with Route Prefix? - c#

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.

Related

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; }
}

How does ASP.NET Core Web API build URLs?

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

ASP.NET Web Api 2 routing convention strange implementation

We all know Web API 2 routing conventions and how we can use the following to automatically map onto ApiController types and actions:
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{action}"
);
public class AbcdefghijklmnoController : ApiController
{
public int Test()
{
return 5;
}
}
This works as expected, I get 5 when calling api/Abcdefghijklmno/test. But now I want to create Controller types that don't explicitely end with Controller in the end, combined with a custom Controller discovery mechanism as outlined in http://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/.
public class Abcdefghijklmno : ApiController
{
public int Test()
{
return 5;
}
}
The crazy thing is now that I can get the value 5 from route api/abcde/test, so what the convention based routing is really doing is simply chopping off the length of the string "Controller" from the type's name. Apart from this extremely naive implementation from Web API 2, is there any way to circumvent this?

Attribute routing not hitting the action methods inside controllers

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

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