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}");
routes.MapMvcAttributeRoutes();
}
}
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
{
public ActionResult Index()
{
ViewBag.Message = "You are in Home Index";
return View();
}
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]
public ActionResult Index()
{
ViewBag.Message = "You are in Home Index";
return View();
}
[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();
}
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)
{
}
[Route("home/{name}")]
public ActionResult Employee(string name)
{
}
}
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
- Some of the constraints, such as “
min
”, “maxlength
”, “minlength
” take arguments in parentheses.
- 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
- Maarten Balliauw’s blog
- Attribute routing in Web api 2
- http://benfoster.io/blog/improving-aspnet-mvc-routing-configuration