Attribute Routing in ASP.NET MVC 5

What is attribute routing?

Routes are the entry points into our MVC application. MVC 5 supports a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web application.

Enabling Attribute Routing

To enable attribute routing, call MapMvcAttributeRoutes during configuration.

 public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapMvcAttributeRoutes();
        }
    }

We can also combine attribute routing with convention-based routing.

public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapMvcAttributeRoutes();

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }

    }

Route URLs

A route’s job is to map a request to an action. The easiest way to do this is using an attribute directly on an action method:

public class HomeController : Controller
    {
        [Route("about")]
        public ActionResult About()
        {
            return View();
        }
    }

This route attribute will run About method any time a request comes in with /about as the URL.

If we use more than one URL for action, we use multiple route attributes. For example, we might want home page to be accessible through the URLs /, /home, and /home/index. Those routes would look like this:

        [Route("")]
        [Route("home")]
        [Route("home/index")]
        public ActionResult Index()
        {
            return View();
        }

Route Values

The static routes you saw earlier are great for the simplest routes, but not every URL is static. For
Example, if action shows the details for a person record, we might want to include the record ID in the URL. That’s solved by adding a route parameter:

        [Route("person/{id}")]
        public ActionResult Details(int id)
        {
            // Do some work
            return View();
        }

We use curly braces around id creates a placeholder for some text that we want to reference by
name later.

Also we want to capturing a path segment, which is one of part of a URL path separated
by slashes (but not including the slashes). To see how that works, let’s define a route like this:

        [Route("{year}/{month}/{day}")]
        public ActionResult Index(string year, string month, string day)
        {
            // Do some work
            return View();
        }

This route matches a URL with three segments, the text in the first segment of that URL corresponds to the {year} route parameter, the value in the second segment of that URL corresponds to the {month} route parameter, and the value in the third segment corresponds to the {day} parameter.

Controller Routes

We have seen peviously how to put route attributes directly on action methods. But often, the methods
In controller class will follow a pattern with similar route templates. For example:

 public class HomeController : Controller
    {
        [Route("home/index")]
        public ActionResult Index()
        {
            return View();
        }
        [Route("home/about")]
        public ActionResult About()
        {
            return View();
        }
        [Route("home/contact")]
        public ActionResult Contact()
        {
            return View();
        }
    }

These routes are all the same except for the last segment of the URL. We would find some way to avoid repeating home on all action method for this we can use route attribute above the controller:

    [Route("home/{action}")]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult About()
        {
            return View();
        }
        public ActionResult Contact()
        {
            return View();
        }
    }

Some actions on a controller will have slightly different routes from all the others. In that case, we can put the most common routes on the controller and then override these defaults on the actions with different route patterns. For example, maybe you think /home/index is too verbose and you want to support /home as well. You could do that as follows:

    [Route("home/{action}")]
    public class HomeController : Controller
    {
        [Route("home")]
        [Route("home/index")]
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult About()
        {
            return View();
        }
        public ActionResult Contact()
        {
            return View();
        }
    }

In the above route rule every route begins with home/ (the class is named HomeController, after all). We can use it just once, using RoutePrefix:

    [RoutePrefix("home")]
    [Route("{action}")]
    public class HomeController : Controller
    {
        [Route("")]
        [Route("index")]
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult About()
        {
            return View();
        }
        public ActionResult Contact()
        {
            return View();
        }
    }

Now, all your route attributes can omit home/ because the prefix provides that automatically. The
prefix is just a default, and you we escape from it if necessary.

For example, you might want home controller to support the URL / in addition to /home and /home/index. To do that, just begin the route template with ~/, and the route prefix will be ignored. Here’s how it looks when HomeController supports all three URLs for the Index method (/, /home, and /home/index):

    [RoutePrefix("home")]
    [Route("{action}")]
    public class HomeController : Controller
    {
        [Route("~/")]
        [Route("")] // We can shorten this to [Route] if you prefer.
        [Route("index")]
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult About()
        {
            return View();
        }
        public ActionResult Contact()
        {
            return View();
        }
    }

Route Constraints

Route constraints let you restrict how the parameters in the route template are matched. The general syntax is {parameter:constraint}. For understand the route constraints we use earlier example with a record ID:

        [Route("person/{id}")]
        public ActionResult Details(int id)
        {
            // Do some work
            return View();
        } 

For this route, we think about what happens when a request comes in for the URL /person/bob. It sees that the action method declares its id method parameter to be an int, and the value bob from the Routing route parameter can’t be converted to an int. So the method can’t execute.

If we wanted to support both /person/bob and /person/1 we might try to add a method overload with a different attribute route like this:

        [Route("person/{id}")]
        public ActionResult Details(int id)
        {
            // Do some work
            return View();
        }
        [Route("person/{name}")]
        public ActionResult Details(string name)
        {
            // Do some work
            return View();
        }

In the above route there is problem one route uses a parameter called id and the other uses a parameter called name. It might seem obvious to you that name should be a string and id should be a number, but to Routing they’re both just route parameters, and, route parameters will match any string by default. So both routes match /person/bob and /person/1. The routes are ambiguous, and there’s no good way to get the right action to run when these two different routes match.

For resolve the above problem we use route constraints. A route constraint is a condition that must be satisfied for the route to match. In this case, we just need a simple int constraint:

        [Route("person/{id:int}")]
        public ActionResult Details(int id)
        {
            // Do some work
            return View();
        }
        [Route("person/{name}")]
        public ActionResult Details(string name)
        {
            // Do some work
            return View();
        }

Optional URI Parameters and Default Values

We can make a URI parameter optional by adding a question mark to the route parameter. We can also specify a default value by using the form parameter=value.

    [RoutePrefix("contacts")]
    [Route("{action}/{id?}")]
    public class ContactsController : Controller
    {
        public ActionResult Index()
        {
            // Show a list of contacts
            return View();
        }
        public ActionResult Details(int id)
        {
            // Show the details for the contact with this id
            return View();
        }
        public ActionResult Update(int id)
        {
            // Display a form to update the contact with this id
            return View();
        }
        public ActionResult Delete(int id)
        {
            // Delete the contact with this id
            return View();
        }

    }

We can provide multiple default or optional values. The following snippet demonstrates providing a
default value for the {action} parameter, as well:

  [Route("{action=Index}/{id?}")]

The above example supplies a default value for the {action} parameter within the URL. Typically the
URL pattern of contacts/{action} requires a two-segment URL in order to be a match. But by supplying a default value for the second parameter, this route no longer requires that the URL contain two segments to be a match. The URL might now simply contain /contacts and omit the {action} parameter to match this route. In that case, the {action} value is supplied via the default value rather than the incoming URL.

Route Names

We can specify a name for a route, in order to easily allow URI generation for it. For example, for the following route:

        [Route("ProductDetails", Name = "Details")]
        public ActionResult Details(int id)
        {
            // Show the details for the contact with this id
            return View();
        }

We could generate a link using Url.RouteUrl:

<a href="@Url.RouteUrl("Details")">Product Details</a>

Areas

We can define that a controller belongs to an area by using the [RouteArea] attribute. When doing so, we can safely remove the AreaRegistration class for that area.

    [RouteArea("Admin")]
    [RoutePrefix("home")]
    [Route("{action}")]

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        [Route("show-about")]
        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }       
    
    }

With this controller, the following link generation call will result with the string “/Admin/home/show-about”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>