What's the best way to generically map routes with required parameters? - c#

I have several ASP.NET MVC controllers. Many of these take one or more required values (e. g. ids). Because these values are required, I'd like to make them part of the url path rather than query string arguments. For example:
// route should be MyController/Action1/[someKindOfId1]
public ActionResult Action1(int someKindOfId1) { ... }
// less commonly:
// route should be MyController/Action1/[someKindOfId2]/[someKindOfId3]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
I'm looking for a way to Map these routes without manually listing out each one. For example, I currently do:
routes.MapRoute(
"Action1Route",
"MyController/Action1/{someKindOfId1}",
new { controller = "MyController", action = "Action1" }
);
Some ways I've considered:
* Use the default {controller}/{action}/{id} route, and just either rename my parameters to id or (not sure if this works) use the [Bind] attribute to allow bind them to the id route value while still having descriptive names. This still restricts me to a common controller/action base URL (not bad, but not the most flexible either as it ties URLs to the current code organization).
* Create an attribute which I could put on action methods to configure their routes. I could then reflect over all controllers and configure routes on application start.
Is there a best-practice/built-in approach for doing this?

Sadly, no. The method you describe is the only way with MVC Routing. If you're not going to use the default (or at least your own version of the default), you must add a separate route for each unique scheme.
However, I would encourage you to check out AttributeRouting, which for me at least, is far superior to managing routes in the traditional way. With AttributeRouting, you specify the URL for each controller action using, appropriately enough, an attribute. For example:
[GET("MyController/Action1/{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("MyController/Action1/{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
Only, you're not bound to using the controller/action route scheme either, so you can do something like:
[GET("foo/{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("foo/{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
And to even better, you can add a RoutePrefix attribute to your controller itself to specify a path partial that should apply to all actions in that controller:
[RoutePrefix("foo")]
public class MyController : Controller
{
[GET("{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
}
There's support for handling areas, subdomains, etc. as well and you can even type-qualify parameters (e.g. {someKindOfId1:int} to make it only match if the URL part is an integer type). Give the documentation a read.
UPDATE
It's worth mentioning that ASP.NET 5 now has attribute routing built in. (It's actually using very similar code to AttributeRouting, submitted by the author of that package.) It's not really a good enough reason on its own to upgrade all your projects (since you can just add in the AttributeRouting package to get basically the same functionality), but if you're starting off with a new project, it's definitely nice to have.

Related

asp.net core route values to base controller and optionally override actions

I have a base controller where most of my api logic sits. I want to standardise the api verbs and I want this base controller to handle most request by default unless I inherit from this controller and override the specific action.
Given a path like this: "/api/Socks/Get?apiKey=1" I am able to do something like this:
[Route("api/[controller]/[action]")]
public class RestDbApiController : Controller
{
[HttpGet]
public virtual async Task<JsonResult> Get(string apiKey = null) {
.....
public class SockController : RestDbApiController
{}
This works okay - ie. the request is routed the action on the base controller. The issue is that I have to declare the SockController, otherwise the request will not route.
I would like to be able to route all requests to "/api/xxxxx" to the base controller without having to declare any other controllers. Please let me know if there is any way to do this.
Why do I want to do this?
I'm trying to make a generic controller with external scripted definitions. Depending on the controller name it would read the script from a similarly named script file. I want to be able to just add the script file to a directory and have it work just like that without having to make any declarations in the code
This isn't really practically possible. You can technically just bind a wildcard route to the controller, but that will then swallow everything. In other words, all your API routes would hit this base controller forever, and you then have to basically set up your own routing infrastructure inside that base controller to reroute requests to the right controller. In case it's not obvious, that's an extremely bad idea.
The best thing to do is actually the thing you don't want to do: actually define the derived controller. While you may look at this like it's extraneous, it's actually not at all. It serves to make your code self-documenting. It's clear that this is a controller that deals with "socks", but it doesn't have any specific logic of it's own. That's perfectly okay.
I agree with #Chris, but if you want to have only one controller, you could omit the "controller name" part from the routing definition. For example, the following requests will be mapped to below action method
/api/Socks/Get?apiKey=1
/api/OtherSocks/Get?apiKey=1
and name parameter will be filled as "Socks", "OtherSocks" respectively:
public class RestDbApiController : Controller
{
[HttpGet]
[Route("api/{name}/[action]")]
public virtual async Task<JsonResult> Get(string name, string apiKey = null)
{
...
}
}

Conflicting routes in ASP.NET Core when using Attribute based routing

Context
I am trying to build an ASP.NET Core web API controller that will expose the following methods with specific semantics:
/api/experimental/cars — get entire collection
[HttpGet("/api/experimental/cars/")]
public Task<List<Car>> AllCars()
/api/experimental/cars/123 — get car by id "123"
[HttpGet("/api/experimental/cars/{carId}")]
public Task<Car> CarById([FromRoute] string carId)
/api/experimental/cars?nameFilter=Maz — get cars that match nameFilter = "Maz"
[HttpGet("/api/experimental/cars/{nameFilter?}")]
public Task<List<Car>> CarsByNameFilter([FromQuery] string nameFilter = "")
/api/experimental/cars?nameFilter=Maz&rating=2 — get cars that match nameFilter = "Maz" and with rating greater or equal to 2
[HttpGet("/api/experimental/cars/{nameFilter?}/{rating?}")]
public Task<List<Car>> CarsByNameAndRatingFilter([FromQuery] string nameFilter = "", [FromQuery] int rating = 1)
Note: I really want to keep the controller class clean and have a single method per Web API route — is it possible?
Problem
As you could guess, there's an issue with these API definitions. Basically, AllCars is intercepting pretty much all the requests. (When I was able to get at least the /api/experimental/cars/{carId} working, the query-string based APIs were still not working and intercepted by another method...
I tried many possible route syntaxes to express what I want with no luck. Is it even possible to use the default routing mechanism or I need to implement my own Router class or Middleware or something else?
Update 1: Problem definition
I know I can join at least three methods and their routes into a single WebAPI method that is being smart about the received parameters. Notice that this is exactly what I am trying to avoid.
Why?
Reason 1: I saw that in non-.NET routers, it worked well and there's no technical impossibility to implement semantic based route resolution.
Reason 2: I perceive all four URL patterns mentioned above as four different routes. One may not agree with me and it's okay, but for my purposes the methods and the routes are different and have to stay different.
Reason 3.1: This keeps controller code clean. Every method only handles one specific case. Parameter names are sufficient to properly resolve the routes (at least in humans head, therefore machine can do it too -- it's easy to formalize the algorithm). If client make a request with an unsupported query parameter, it should result in HTTP 404 Not Found or HTTP 400 Bad Request -- totally fine (client rather construct correct URLs).
Reason 3.2: On contrary, if I join the methods and use a more generic route, my implementation needs to be 'smart' about the combination of parameters. This is effectively, a leak of routing abstractions into a layer where it does not belong in my architecture. Complex validation is another thing I don't want to see in the Controller -- less code is better.
Update 2: Nancy — Another .NET example (other that .NET Core WebApi)
There is Nancy (a .NET framework) which perfectly deals with this aspect of routing: https://github.com/NancyFx/Nancy/wiki/Defining-routes#pattern The issue is that in my project we're not using it... Nancy works as a perfect example of a tool that leaves exact definition of routing semantics to the client, instead of enforcing too tight rules on what is the route vs what is not.
You could Achieve this with just two routes:
[HttpGet("/api/experimental/cars/")]
public Task<List<Car>> SearchCars([FromQuery] string nameFilter = "", [FromQuery] int rating = 1)
and
[HttpGet("/api/experimental/cars/{carId}")]
public Task<Car> CarById([FromRoute] string carId)
I.e one route which brings the entire set back but can be filtered accordingly and one the brings back a single Car object by Id.
You will notice that the SearchCars method doesn't include the parameters in the route, FromQuery will catch these anyway.
EDIT:
if your request becomes complex it can be nice to define a custom request object type to wrap all your filters together:
public class MyRequestObject
{
public string NameFilter {get;set;}
public int Rating {get;set;}
}
then:
[HttpGet("/api/experimental/cars/")]
public Task<List<Car>> SearchCars([FromQuery] MyRequestObject requestParams)
Take a look at the following suggested routes that when tested do not conflict with each other and still allow for all the actions to be segregated.
[Route("api/experimental/cars")]
public class CarsController : Controller {
//GET api/experimental/cars
[HttpGet("")]
public IActionResult AllCars() { ... }
//GET api/experimental/cars/123
[HttpGet("{carId}")]
public IActionResult CarById(string carId) { ... }
//GET api/experimental/cars/named/Maz
//GET api/experimental/cars/named?filter=Maz
[HttpGet("named/{filter?}")]
public IActionResult CarsByNameFilter(string filter = "") { ... }
//GET api/experimental/cars/filtered?rating=2&name=Maz
//GET api/experimental/cars/filtered?rating=2
//GET api/experimental/cars/filtered?name=Maz
[HttpGet("filtered")]
public IActionResult CarsByNameAndRatingFilter(string name = "", int rating = 1) { ... }
}
My experience with this topic tells me that the best way to implement the APIs I wanted is to have two methods:
class CarsController {
// [HttpGet("/api/experimental/cars/")]
[HttpGet("/api/experimental/cars/{carId}")]
public Task<IEnumerable<Car>> CarById([FromRoute] string carId)
{
if (carId == null)
return GetAllCars();
else
return GetCarWithId(carId);
}
// [HttpGet("/api/experimental/cars/{nameFilter?}")]
[HttpGet("/api/experimental/cars/{nameFilter?}/{rating?}")]
public Task<IEnumerable<Car>> CarsByNameAndRatingFilter([FromQuery] string nameFilter = "", [FromQuery] int rating = 1)
{
// TODO Validate the combination of query string parameters for your specific API/business rules.
var filter = new Filter {
NameFilter = nameFilter,
Rating = rating
};
return GetCarsMatchingFilter(filter);
}
}
The first API is almost trivial. Even though returning a single item within a wrapping collection object may not look nice, it minimizes the number of API methods (which I personally am fine with).
The second API is trickier: in a way, it works as the façade pattern. I.e. that API will respond to pretty much all the possible /api/experimental/cars? based routes. Therefore, we need to very carefully validate the combination of received arguments before doing the actual work.

Populate the Defaults RouteValueDictionary with attribute routing

I'm upgrading an ASP.NET MVC 4 project to MVC 5 and want to use attribute routing instead of convention routing. So far, so good, but I have one issue with populating the Defaults RouteValueDictionary. How can this be accomplished with attribute routing?
I am using multiple routes for the same action, each passing a different enum value to determine which type the Action is. The value of the enum will not be visible in the route directly though! This is important, otherwise I could use the value of the enum parameter in the route template.
My simplified Controller Action:
public class MyController : Controller
{
public ActionResult MyAction(MyType myTypeValue)
{
// ...
}
}
public enum MyType
{
FirstOption,
SecondOption
}
My old convention routes:
routes.Add("First", new Route("a-route", new { controller = "MyController", action = "MyAction", myTypeValue = MyType.FirstOption }));
routes.Add("Second", new Route("a-total/different-route", new { controller = "MyController", action = "MyAction", myTypeValue = MyType.Second }));
With attribute routing i was expecting to use something like this:
Route["a-route", new { myTypeValue = MyType.FirstOption }]
Route["a-total/different-route", new { myTypeValue = MyType.SecondOption }]
But unfortunately, this does not exists. I've tried to make a custom RouteAttribute that accepts an object to populate the Defaults RouteValueDictionary:
public class MyRouteAttribute : RouteFactoryAttribute
{
private RouteValueDictionary _defaults;
public Route(string template, object defaults)
:base(template)
{
_defaults = new RouteValueDictionary(defaults);
}
public override RouteValueDictionary Defaults
{
get { return _defaults; }
}
}
But this is not working since the route attribute cannot handle anonymous types compile time.
Does anyone know a way to get this working one way or another?
"Just make two different actions" is not an option here.
First of all, it is unclear why you would want to change from convention-based routing to the (less flexible) attribute-based routing, especially considering some of the features you are interested in are not supported by the latter.
But if you are insistent on changing to attribute routing just because it "looks cool", then you have a couple of options.
Option 1: Make Separate Action Methods
If you use 2 different actions and return one action from the first, you generally won't have to rewrite logic. But this is the only native support in attribute routing for setting optional parameters. An example of how you can support optional parameters with Enum can be found here.
[Route("a-route")]
public ActionResult MyAction(MyType myTypeValue = MyType.FirstOption)
{
return View("Index");
}
[Route("a-total/different-route")]
public ActionResult My2ndAction(MyType myTypeValue = MyType.SecondOption)
{
return MyAction(myTypeValue);
}
Option 2: Hack the Attribute Routing Framework
Microsoft intentionally made the attribute routing framework non-extensible by using several internal/private types to load the RouteValueCollection with the attribute routes.
You could potentially hack the attribute routing framework to provide your own logic as I have done here. This requires using Reflection, but since it runs at the start of the application rather than per-request the overall performance impact will be minimal.
But depending on your requirements, you may need to copy more of the logic from the MVC attribute routing framework to populate your routes, which may not be worth the effort. In my simple case of supporting multiple cultures it was. In your case you will need to support your own attribute types with additional parameters, which will be more challenging.
But if you need more flexibility than this, I would suggest sticking with the convention-based routing.
Attributes have limitations on which datatypes are supported as opposed to code-based solutions.
Several features including populating default route values, using constraints, and building custom routes are either much more difficult or not supported when using attribute routing.
The bottom line is, attribute routing is not the holy grail of routing. It is another routing option added in MVC 5 which can be used under a limited subset of routing scenarios of which convention-based routing is capable of. It is not and should not be viewed as a routing "upgrade" just because it happens to not have been an option until MVC 5.

RoutePrefix Order alternative for WebAPI 2

In WebAPI you can specify an Order in RouteAttribute to determine which order the routes are matched in. For example the below will match /other to GetOther before matching /blah to GetByName
[HttpGet, Route("{name}", Order = 1)]
public string GetByName(string name) { ... }
[HttpGet, Route("other")]
public string GetOther() { ... }
How would I do the same but with RoutePrefix (which doesn't have an Order property)? If it did it would looks something like this:
[RoutePrefix("foo", Order = 1)]
public class FooController : ApiController { ... }
[RoutePrefix("foo/bar")]
public class FooBarController : ApiController { ... }
Doing the above (without the imaginary Order property) throws the following message when calling /foo/bar:
Multiple controller types were found that match the URL
Is there existing functionality for getting around this (preferably with attributes)?
I don't believe Microsoft's attribute routing has support for ordering routes by controller.
When you specify an Order property on an action's RouteAttribute, you are specifying the order within the controller only.
AFAIK, the attribute routing algorithm will scan all of the controllers alphabetically. Then within each controller, is will use the Order property of any RouteAttributes to decide the order of action routes within that controller.
This means if you have route collisions spread across different controllers, you should either rethink the design or make sure the controllers with the more specific route patterns are named alphabetically before the controllers with the more general route patterns. Otherwise, you may run into that "ambiguous route / multiple actions with matching routes found" exception.
Update: The answer above is for Microsoft's AttributeRouting implementation, which was based on another very popular open source project that came before MVC5. In that library, you could order attribute routes by controller, though I think the property was SiteOrder or something like that.
You can add a orderby to the loop in index.cshtml:
#foreach (var group in apiGroups.OrderBy(g => g.Key.ControllerName))

Why does ApiController require explicit Http verbs on methods?

I'm using ApiController. I struggle to understand why ApiControllers differ from Controllers in certain ways.
Take
public class QuestionCommentController : ApiController
{
QuestionCommentCRUD crud = new QuestionCommentCRUD();
// GET api/questioncomment/5
[HttpGet]
public string Read(int id)
I'm accustomed to Controller types which allow me to create method without specifying the legal verbs via Attributes:
public class QuestionCommentController : Controller
{
QuestionCommentCRUD crud = new QuestionCommentCRUD();
// GET questioncomment/5
public string Read(int id)
In the latter case I can perform GET/POST without specifying HttpGetAttribute. I find this behavior confusing for a few reasons:
There's now two HttpGet: System.Web.Http.HttpGet and System.Web.Mvc.HttpGet
System.Web.Http.HttpGet is required, System.Web.Mvc.HttpGet is not required for GET requests
ApiController requests require a unique route /api/controller...
Controller's allows me to fall into the pit of success. The newer ApiController requires hand holding.
I noticed the default template has a syntax that I don't understand:
public void Post([FromBody]string value)
{
}
The verb is the method name along with some funky [FromBody] thing going on. Maybe this is why things are setup this way? What assumptions exist about the usage of ApiController that led to this design?
Your API controllers don't require verbs on the methods if you follow the built in convention. If you prefix your method names with the proper verb, Get, Post, etc. there's no need to decorate with the attribute.
In your case.
public string GetRead(int)
and a live example from a project I'm currently working on
[Authorize]
public HttpResponseMessage GetStoreList([FromUri]NamedViewModel model)
No decoration necessary, so System.Web.Http.HttpGet is not required.
You can do it the way you have listed above or the way I have it. WebApi is allowing you to do either REST or RPC style calls as you see fit. That's why you see the differences. The inclusion of support for RESTful style calls required additional work.
I will agree that the two separate HttpGet attributes is confusing, especially when both are included in a WebApi project out of the box. That bit me a few times when I accidentally included the wrong namespace.

Categories

Resources