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...
As a rule of thumb I try to:
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
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
Resources: jarrettmeyer.com/...
Nested Resources: jarrettmeyer.com/...
http://maproutes.codeplex.com/
Example:
[Route("product/{name}")]
[RouteDefault("name", "")]
[RouteConstraint("name", "^[0-9]+_")]
public ActionResult Product(string name)
{
return View();
}
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.
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.
Although obviously this does inhibit future functionality if you want to create a new controller that happens to share the name of a user. :)
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
Great job, Congrats.