Easier ASP.NET MVC Routing
Posted
by Steve Wilkes
on Geeks with Blogs
See other posts from Geeks with Blogs
or by Steve Wilkes
Published on Wed, 30 Nov 2011 19:46:00 GMT
Indexed on
2011/12/01
1:56 UTC
Read the original article
Hit count: 598
I've recently refactored the way Route
s are declared in an ASP.NET MVC application I'm working on, and I wanted to share part of the system I came up with; a really easy way to declare and keep track of ASP.NET MVC Route
s, which then allows you to find the name of the Route
which has been selected for the current request.
Traditional MVC Route Declaration
Traditionally, ASP.NET MVC Route
s are added to the application's RouteCollection
using overloads of the RouteCollection.MapRoute()
method; for example, this is the standard way the default Route
which matches /controller/action URLs is created:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
The first argument declares that this Route
is to be named 'Default', the second specifies the Route
's URL pattern, and the third contains the URL pattern segments' default values. To then write a link to a URL which matches the default Route in a View, you can use the HtmlHelper.RouteLink()
method, like this:
@ this.Html.RouteLink("Default", new { controller = "Orders", action = "Index" })
...that substitutes 'Orders' into the {controller} segment of the default Route
's URL pattern, and 'Index' into the {action} segment. The {Id} segment was declared optional and isn't specified here.
That's about the most basic thing you can do with MVC routing, and I already have reservations:
- I've duplicated the magic string
"Default"
between theRoute
declaration and the use ofRouteLink()
. This isn't likely to cause a problem for the defaultRoute
, but once you get to dozens ofRoute
s the duplication is a pain. - There's no easy way to get from the
RouteLink()
method call to the declaration of theRoute
itself, so getting the names of theRoute
's URL parameters correct requires some effort. - The call to
MapRoute()
is quite verbose; with dozens ofRoute
s this gets pretty ugly. - If at some point during a request I want to find out the name of the
Route
has been matched.... and I can't.
To get around these issues, I wanted to achieve the following:
- Make declaring a
Route
very easy, using as little code as possible. - Introduce a direct link between where a
Route
is declared, where theRoute
is defined and where theRoute
's name is used, so I can use Visual Studio's Go To Definition to get from a call toRouteLink()
to the declaration of theRoute
I'm using, making it easier to make sure I use the correct URL parameters. - Create a way to access the currently-selected
Route
's name during the execution of a request.
My first step was to come up with a quick and easy syntax for declaring Route
s.
1 . An Easy Route Declaration Syntax
I figured the easiest way of declaring a route was to put all the information in a single string with a special syntax. For example, the default MVC route would be declared like this:
"{controller:Home}/{action:Index}/{Id}*"
This contains the same information as the regular way of defining a Route
, but is far more compact:
- The default values for each URL segment are specified in a colon-separated section after the segment name
- The {Id} segment is declared as optional simply by placing a * after it
That's the default route - a pretty simple example - so how about this?
routes.MapRoute(
"CustomerOrderList",
"Orders/{customerRef}/{pageNo}",
new { controller = "Orders", action = "List", pageNo = UrlParameter.Optional },
new { customerRef = "^[a-zA-Z0-9]+$", pageNo = "^[0-9]+$" });
This maps to the List
action on the Orders
controller URLs which:
- Start with the string Orders/
- Then have a {customerRef} set of characters and numbers
- Then optionally a numeric {pageNo}.
And again, it’s quite verbose. Here's my alternative:
"Orders/{customerRef:^[a-zA-Z0-9]+$}/{pageNo:^[0-9]+$}*->Orders/List"
Quite a bit more brief, and again, containing the same information as the regular way of declaring Route
s:
- Regular expression constraints are declared after the colon separator, the same as default values
- The target controller and action are specified after the ->
- The {pageNo} is defined as optional by placing a * after it
With an appropriate parser that gave me a nice, compact and clear way to declare routes. Next I wanted to have a single place where Route
s were declared and accessed.
2. A Central Place to Declare and Access Routes
I wanted all my Route
s declared in one, dedicated place, which I would also use for Route
names when calling RouteLink()
. With this in mind I made a single class named Routes
with a series of public, constant fields, each one relating to a particular Route
. With this done, I figured a good place to actually declare each Route
was in an attribute on the field defining the Route
’s name; the attribute would parse the Route
definition string and make the resulting Route
object available as a property. I then made the Routes
class examine its own fields during its static setup, and cache all the attribute-created Route
objects in an internal Dictionary
. Finally I made Routes
use that cache to register the Route
s when requested, and to access them later when required.
So the Route
s class declares its named Route
s like this:
public static class
Routes
{
[RouteDefinition("Orders/{customerName}->Orders/Index")]
public const string OrdersCustomerIndex = "OrdersCustomerIndex";
[RouteDefinition("Orders/{customerName}/{orderId:^([0-9]+)$}->Orders/Details")]
public const string OrdersDetails = "OrdersDetails";
[RouteDefinition("{controller:Home}*/{action:Index}*")]
public const string Default = "Default";
}
...which are then used like this:
@ this.Html.RouteLink(Routes.Default, new { controller = "Orders", action = "Index" })
Now that using Go To Definition on the Routes.Default
constant takes me to where the Route
is actually defined, it's nice and easy to quickly check on the parameter names when using RouteLink()
. Finally, I wanted to be able to access the name of the current Route
during a request.
3. Recovering the Route Name
The RouteDefinitionAttribute
creates a NamedRoute
class; a simple derivative of Route
, but with a Name
property. When the Routes
class examines its fields and caches all the defined Route
s, it has access to the name of the Route
through the name of the field against which it is defined. It was therefore a pretty easy matter to have Route
s give NamedRoute
its name when it creates its cache of Route
s. This means that the Route
which is found in RequestContext.RouteData.Route
is now a NamedRoute
, and I can recover the Route
's name during a request. For visibility, I made NamedRoute.ToString()
return the Route
name and URL pattern, like this:
The screenshot is from an example project I’ve made on bitbucket; it contains all the named route classes and an MVC 3 application which demonstrates their use. I’ve found this way of defining and using Routes much tidier than the default MVC system, and you find it useful too
© Geeks with Blogs or respective owner