KISS Your ASP.NET MVC Routes

Monday, January 25, 2010

A little bit of thinking and compromise can remove unnecessary complexity from the routes in an MVC application.

For example:

Morgan’s web site has authenticated users,and Morgan decides the URLs for managing users should look like /morgan/detail. Morgan adds the following code to register the route:

routes.MapRoute(
    "User",
    "{username}/{action}",
    new { controller ="User", action="Index" }
);

Morgan runs some tests and it looks like there is a problem. The User controller is getting requests for /home/index. Oops, that request should have gone to the Home controller. Fortunately, Morgan knows there is all sorts of whizz-bang features you can add to a route, so Morgan creates a route constraint:

public class UserRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, 
                      Route route, string parameterName, 
                      RouteValueDictionary values, 
                      RouteDirection routeDirection)
    {
        var username = values["username"] as string;
        return IsCurrentUser(username);
    }

    private bool IsCurrentUser(string username)
    {
        // ... look up the user
    }
}

The constraint prevents the User route from gobbing requests to other controllers. It only allows requests to reach the User controller when the username exists in the application.

Morgan tweaks the route registration code to include the constraint:

routes.MapRoute(
    "User",
    "{username}/{action}",
    new { controller ="User", action="Index"},
    new { user = new UserRouteConstraint() }
);

Morgan runs some tests and everything works!

Well …

Everything works until a user registers with the name “home”. Morgan goes off to tweak the code again...

STOP!

As a rule of thumb I try to:

  • Never use advanced routing features like constraints
  • Always stick to a URL like {controller}/{action}/{id} or {controller}/{id}/{action}

Sometimes you have to go break those rules, but let’s look at Morgan’s case. If Morgan was happy with a URL like /user/index/morgan, then Morgan wouldn’t need to register any additional routes. The default routing entry in a new MVC application would happily invoke the index action of the User controller and pass along the username as the id parameter. Morgan could also go with /user/morgan and use a simple route entry like this:

routes.MapRoute(
    "User",
    "User/{username}/{action}",
    new { controller ="User", action="Index", username="*"}
);

No constraints required!

The KISS principle (keep it simple stupid) is a good design principle for routes. You’ll have fewer moving parts, consistent URL structures, and make the code easier to maintain.


Comments
gravatar Ryan Rivest Tuesday, January 26, 2010
I agree completely. I try to avoid adding complex routes as much as possible, because they can have a lot of unexpected side effects.
gravatar Cyril Gupta Tuesday, January 26, 2010
Hello Allen,

I agree with your KISS principle about routes. I have never used constraints as yet in my MVC apps. In fact what I am doing is building generic routes with {param1}, {param2}, type of structure. So I have only 9 routes configured and I manage to run my entire website on these 9 routes without any hassles.

Cheers!
Cyril Gupta
codebix.com
gravatar Colin Jack Tuesday, January 26, 2010
Definitely agree, in the vast majority of cases I think simple URIs and routes are better.
gravatar Rick Schott Tuesday, January 26, 2010
MVC Routes should be easily testable, so the more go against the grain, the harder they will be to test. Totally agree with you on KISS.
Paul Tuesday, January 26, 2010
I agree too, but frustratingly when working with SEO in mind, those easy to identify in code URLs might not cut it with Marketing, etc.
gravatar Danny Douglass Wednesday, January 27, 2010
In the vast majority of cases I would agree that more complex routing features such as Constraints are not best practice, readable, etc. I have come across a scenario at my company where we have a custom controller type that uses the same {controller}/{action}/{id} touring as our standard ASP.NET MVC controllers. I found constraints to be useful here as it did not require me to maintain a new route for each named instance of the ASP.NET MVC controllers (could end up being quite a few unique names). Using constraints I only need two routes to solve my problem!
gravatar Danny Douglass Wednesday, January 27, 2010
oops...I meant "routing" not "touring"... dyslexic much?? =]
gravatar Jarrett Monday, February 1, 2010
Mostly, I agree, with one little exception of resource-style routes and nested routes. Rails-style routes without the extra parts are really nice! (ex: A delete request automatically goes to /Users/1/Delete, but without the 'Delete'.)

Resources: jarrettmeyer.com/...
Nested Resources: jarrettmeyer.com/...
gravatar Koistya `Navin Wednesday, February 3, 2010
Automatic route registration by using controller method's attributes:

http://maproutes.codeplex.com/

Example:

[Route("product/{name}")]
[RouteDefault("name", "")]
[RouteConstraint("name", "^[0-9]+_")]
public ActionResult Product(string name)
{
return View();
}
gravatar Scott Allen Wednesday, February 3, 2010
@Koistya

I personally don't like the attribute approach. I think it scatters what should be simple declarations far and wide across the code base and only increases the chances for collisions.
gravatar rsenna Thursday, February 25, 2010
I'll have to disagree.

Consider that

1) Controllers are a reflection of software design. They are related to the internal organization of your system, and are NOT created for the sake of "pretty urls".

2) On the other side, your final URLs should be friendly. URLs are created for users, and for SEO optimization, NOT for the sake of developers.

So there is a abyss here, right? Using ASP.NET MVC (and, in fact, many other MVC frameworks, like Castle Monorail or Cake PHP) my urls, by default, are a reflection of the way my controllers and actions were created, and are not necessarily the most "friendly", thinking at the user level.

I correct this impedance through the use of routes.

So no, i do not agree that you should avoid using routes. They should be kept as simple as possible, but NOT to the cost of not-so-friendly urls (which means bad usabillity), and definitely NOT to the cost of creating controllers just for the sake of nice urls (which means bad software design).

Sorry 'bout my english; I hope I made myself clear.

gravatar RebeccaBENJAMIN27 Friday, February 26, 2010
Set your own life time more easy get the loan and everything you require.
gravatar Dan Atkinson Friday, March 5, 2010
Whilst I agree with what you're saying about KISS in general, you could easily get around this problem by checking whether a controller exists with that name!

Although obviously this does inhibit future functionality if you want to create a new controller that happens to share the name of a user. :)
gravatar Doug Rathbone Sunday, March 28, 2010
In most cases i will agree with you. But at the same time i must put some weight on the argument made by @rsenna about URLs being friendly.

It is important to make it as user memory/bookmark friendly as possible

This can be shown in twitter - its so easy to remember someone's twitter URL
gravatar SD!!! Monday, July 5, 2010
Love this article -you express yourself very good- lots of new ideas are coming to mind : D

Great job, Congrats.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!