We saw already the use of convention based routing and we saw Attribute Routing in the construction of an api controller and a restful controller. We will now take a closer look at the use of Attribute Routing and its benefits in contrast to convention based routing.

What this post will cover

  • How to use Attribute Routing
  • Reasons to use Attribute Routing

What this post will not cover

  • Details of convention based routing
  • Outgoing URLs
  • Other related MVC features like controllers etc.

You enable attribute routing by calling the UseMvc method in Startup.cs. When the application sets up the routing middleware (and the other middleware components for that matter) MVC scans all classes that are postfixed with Controller and checks if they have Route Attributes applied. With those classes MVC then creates a route based on this Attribute(s)

To show attribute routing we use the Configure method in the Startup.cs like:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    app.UseDeveloperExceptionPage();
    app.UseStatusCodePages();
    app.UseStaticFiles();
    // this one is important!
    app.UseMvc();
}

You could also use app.UseMvcWithDefaultRoute, where the default matches all Routes that comply to:

{controller}/{action}/{id?}

Route Attribute

The Route Attribute is defined as follows:

[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method, Inherited = true, 
AllowMultiple = true)]
public sealed class RouteAttribute : Attribute, IDirectRouteFactory, IHttpRouteInfoProvider
{
    public string Name {get; set;}
    public int Order {get; set;}
    public string Template {get; set;}
}

With the definition we can see, that a Route can have a Name, an Order and a Template. When you recall how you’d define routes with the middleware directly (aka convention based routing), there is not a big difference here.
To apply the Route Attribute it is either directly applied to a Controller classes or to individual action methods. It can also be stacked as we will soon see, to create more complex routes and to match different routes, similar to the default routing.

public class HomeController : ControllerBase
{
    [Route("myRoute")] 
    public ViewResult Index(){...}

    public ViewResult List(){ ... }
}

The Index method is now available with URL: /myroute. The List method is still available with the URL /Home/List due to no Route Attribute but the DefaultMvcRoute we defined in the Startup class. Due to the Route Attribute on the Index method it is now not longer reachable with the /Home/Index URL!
So Attribute Routing effectively overwrites convention based routing!

You could recreate the default routing behavior with multiple Route Attributes:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    public IActionResult Index()
    {
        return View();
    }

    [Route("Home/About")]
    public IActionResult About()
    {
        return View();
    }

    [Route("Home/Contact")]
    public IActionResult Contact()
    {
        return View();
    }
}
  • Index would be matched by
    • /
    • /Home
    • /Home/Index

Stacking Route Attributes

As I mentioned above you can stack the Route Attribute to create more complex routes. We saw one way to do this in the post about api controllers. You can apply the Route Attribute on a Controller class itself to make sure all Routes for the action methods are prefixed with the given Route:

[Route("myroute")]
public class HomeController
{
...
}

Now all action methods are prefixed with /myroute. The stacking occurs when you apply a Route Attribute to an action method of this controller:

[Route("myroute")]
public class HomeController : ControllerBase
{
    [Route("index")]
    public IActionResult Index()
    {
    ...
    }
}

This would result in a Route that leads to the Index method only if it complies to the URL:
/myroute/index.

Note that you do not need a trailing or a leading forwardslash (“/”) for the specified Route, this will be applied by MVC on creation of the route.

Templates and Tokens

As with the convention based routing you can use Tokens in your patterns/templates provided with the Route Attribute.
We saw with the api controller how the most standard way is to do this:

[Route("api/[controller]/[action]")]
public class ApiController : ControllerBase
{
    public IActionResult Index()
   {
    ...
   }
}

Note (as I pointed out in api controller) that the token for the template is embedded in square brackets instead of curly brackets.

The use of templates and tokens removes the need to hardcode for the route.

In the above example we also used a static segment “/api…”. So nothing new here from convention based routing.
As with convention based routing you can combine Tokens, optional segments and also constraints to form complex routes:
e.g. use the following Attribute

[Route("app/[controller]/actions/[action]/{id?}")]

Constraints in Route attributes work the same way as in convention based routing, even the chaining:

[Route("app/[controller]/actions/[action]/{id:range(10,20)?}")]

Attribute routing with httpverbs

As I explained above with stacking Route Attributes you can also stack the Route Attribute with the HttpVerb Attributes. Those also accept a route template as parameter.

If you now get a request with the URL that matches your defined Route Attribute template and it has the appropriate HttpVerb, it will match to your action method which has the HttpVerb Attribute applied to it

The HttpVerb attributes template is cumulative with the RouteAttribute on the class level, which can be utilized very well for api controllers that are reachable at the same URL but with different Verbs.

You can also apply a route Name on Verb Attributes to use for outgoing URLs. This we will cover in the post about outgoing URLs.

Ordering

Because convention based routing is applied sequentially and attribute routing is more ordered in a tree like structure, there is a need to control the ordering of applied Route Attributes. Routing with Attributes can be thought of as ideally arranged routes, where the most specific routes are placed first in sequence (in reality it builds a tree like structure).
This can then be changed by providing a value to the Order property on the Route Attribute. The value of -1 means that this Route is “run” first. The default Ordering value is 0 and a value of 1 lets the Route run last.

Why use Route Attribute

We now looked at attribute routing and one might ask the question, if it is so similar to convention based routing, why should one use this for routing?

There are several reasons:

  • First of all you will need to do this for api controllers, because else you could not specify http verbs for methods.
  • Secondly you can overwrite the use of convention based routing which can be useful, for different scenarios like testing, redefining your URL structure or others.
  • Thirdly it allows for more explicit routing and more control over the route to action relationship.

So what to use now?

For non api controllers it is a choice of personal preference: Explicit and lot of control over your routes vs. all routes in one place. I personally tend to the attribute routing, because I prefer explicitness over conventions. Also most web applications will have an Api controller, and at the latest with this controller you need to mix convention based routing with attribute routing.

Summary

In this post we briefly looked at the notion of attribute Routing, where it is similar to convention based routing, where it differs from convention based routing and how and when to apply it.

 


0 Comments

Leave a Reply

Avatar placeholder