Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

[Attribute] Routing in ASP.NET MVC 5 / WebAPI 2

0.00/5 (No votes)
18 May 2014 2  
[Attribute] Routing in ASP.NET MVC 5 / WebAPI 2

Introduction

In the last post, I have discussed how the routing framework actually works. In this post, I will discuss about one of the coolest latest features offered by MVC5, WebAPI2 and it's called “Attribute Routing”. Under the hood, attribute routing still maintains the same mechanism of routing framework.

So, right now, you might be thinking if new routing feature (attribute routing) also uses the same routing mechanism under the hood then where the twist actually came in the game? Well, over the last few years while developing large enterprise web apps, it was found that as the project gets bigger and special cases accumulate, it becomes hard to keep track of all those routes in a single file. Things get a bit messy while developers have to write code to apply complex constraint. In most cases, they use a custom constraint by implementing IRouteConstraint and defining the custom logic in the Match method – if it returns true, the route is a match.

public interface IRouteConstraint
{
    bool Match(HttpContextBase httpContext, Route route, 
    string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
} 

The problem with the Convention-based Routing is that since the routes are physically separated from the controllers they apply to, it often take some detective work to understand the relationships. With the hope to give a better development experience of this issue, #Microsoft has adopted “attribute routing” feature in ASP.NET MVC5 and WebApi2 from Tim McCall.

As the name implies, Attribute routing uses attributes to define routes and it can be used on controller actions and even controller classes as well. In short, I will explain how these features makes life easier. But before that, you have to enable this new feature in your solution. So, let's do it first.

Enabling Attribute Routing

To enable Attribute Routing, we need to call the MapMvcAttributeRoutes method of the route collection class during configuration.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        //Add the following line of code
        routes.MapMvcAttributeRoutes(); 
 
       //[Code Excerpt]
    }
}

As stated earlier, you can keep both the “Convention-based Routing” as well as the “[attribute] routing” under the same web app, if so then keep the following note in mind.

MapMvcAttributeRoutes() have to call before the Convention-based Routing.

Now, once we have enabled the attribute routing, let's check out some examples.

Example

Defining a Route

A route attribute has to be defined on top of an action method or on the top of a controller. In the following example, I have declared the attribute routing on top of action method named “About()”.

public class HomeController : Controller
{
       [Route("Users/about")]
       public ActionResult About()
       {
           ViewBag.Message = "You successfully reached USERS/About route";
           return View();
       }
}

Defining a Common Prefix

If you want to specify a common prefix for an entire controller, then instead of specifying [RoutePrefix] attribute on the top of each and every action, you should specify it in the controller level.

[RoutePrefix("Movie")]
public class HomeController : Controller
{
      //Route: Movie/Index
      public ActionResult Index()
      {
           ViewBag.Message = "You are in Home Index";
           return View();
      }
 
      //Route: Movie/About
       public ActionResult About()
       {
           ViewBag.Message = "You successfully reached USERS/About route";
           return View();
       }
} 

Overriding a Common Prefix

There could be situations where you might not want your actions to be response under the same route prefix. In such cases, you have to override the common route prefix. Doing so is easy.

Use a tilde (~) on the method attribute to override the route prefix.

[RoutePrefix("Movie")]
public class HomeController : Controller
{
      //Route: Movie/Index
      [Route]
      public ActionResult Index()
      {
           ViewBag.Message = "You are in Home Index";
           return View();
      }
 
      //Route: NewRoute/About
       [Route("~/NewRoute/About")]
       public ActionResult About()
       {
           ViewBag.Message = "You successfully reached NEWRoute/About route";
           return View();
       }
}

Defining a Default Route

Now you know how to define a common prefix and how to override it in need. Our next target is to define a default route using attribute routing. First apply the [Route] attribute on the Controller level and then specify a default action as a parameter.

In the following code snippet, if not specified anything the routing framework will take the user to “/Movie/index” route.

[RoutePrefix("Movie")]
[Route("{action=index}")]
public class HomeController : Controller
{
      public ActionResult Index()
      {
           ViewBag.Message = "You are in Home Index";
           return View();
      }
 
      //Route: Movie/About
       public ActionResult About()
       {
           ViewBag.Message = "You successfully reached USERS/About route";
           return View();
       }
} 

Note: To override the default route, just specify the specific route on specific action !

Optional URI Parameter

To make any parameter optional, add a question mark to the Route parameter.

public class HomeController : Controller
{
      [Route("home/{id?}")]
      public ActionResult Employee(int id)
      {
           ViewBag.Message = "You are in Home Index";
           return View();
      }
}

Default Value in URI Parameter

To specify any parameter as default, initialize a value in the route parameter.

public class HomeController : Controller
{
      [Route("home/{type=en}")]
      public ActionResult Search(string type)
      {
           ViewBag.Message = "You are in Home Index";
           return View();
      }
}

Imposing Constraints

Imposing constraints restrict how the parameters in the route template are matched. On the top, I have shown you how to search certain employee by their id. Now, let's say we also want the end user to search employee by Name. Also assume different view has be render in that case.

home/3 <–render “View A”

home/shahriar <–render “View B”

What we can do is to add another ActionResult which receives a string parameter. But only doing so does not solve our problem. We have to explicitly define and impose certain constraints to let the routing framework work perfectly.

Notice the route carefully. In both cases, routing framework hits the home controller and then based on the action parameter received it renders different view. If we just don’t explicitly tell the routing framework to impose certain constraints, it just can’t fly you(the viewer) to the appropriate action.

The general syntax for imposing certain constraint is {parameter:constraint}.

public class HomeController : Controller
{
      [Route("home/{id? : int}")]
      public ActionResult Employee(int id)
      {
           //Logic goes here
      }
 
      [Route("home/{name}")]
      public ActionResult Employee(string name)
      {
           //Logic goes here
      }
} 

Here, the first route will only be selected if the “id” segment of the URI is an integer. Otherwise, the second route will be chosen. (“?”) symbol specifies that this parameter is Optional.

Ken Egozi posted a nice article regarding Attribute routing and there he posted a complete list of constraints with some examples that I have added below:

Constraint Description Example
alpha Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) {x:alpha}
bool Matches a Boolean value. {x:bool}
datetime Matches a DateTime value. {x:datetime}
decimal Matches a decimal value. {x:decimal}
double Matches a 64-bit floating-point value. {x:double}
float Matches a 32-bit floating-point value. {x:float}
guid Matches a GUID value. {x:guid}
int Matches a 32-bit integer value. {x:int}
length Matches a string with the specified length or within a specified range of lengths. {x:length(6)}
{x:length(1,20)}
long Matches a 64-bit integer value. {x:long}
max Matches an integer with a maximum value. {x:max(10)}
maxlength Matches a string with a maximum length. {x:maxlength(10)}
min Matches an integer with a minimum value. {x:min(10)}
minlength Matches a string with a minimum length. {x:minlength(10)}
range Matches an integer within a range of values. {x:range(10,50)}
regex Matches a regular expression. {x:regex(^\d{3}-\d{3}-\d{4}$)}

Pay Close Attention

  1. Some of the constraints, such as “min”, “maxlength”, “minlength” take arguments in parentheses.
  2. To apply multiple constraints to a parameter, separated each constraint by a colon.

Rrestricting specific actions to specific HTTP verbs:

Existing attributes (e.g. [HttpGet], [HttpPost] and [AcceptVerbs]) are allowed:)

Conclusion

And this brings us to end of this topic. I have tried to cover up almost all the relevant major things regarding attribute routing but yet there are some mini tips and tricks that haven't been discussed here. I leave those tiny things for you. Hope you could figure out those tiny things by yourself. Happy coding! :)

Additional Resources

  1. Maarten Balliauw’s blog
  2. Attribute routing in Web api 2
  3. http://benfoster.io/blog/improving-aspnet-mvc-routing-configuration

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here