Generic or Specific Routes?
A topic of discussion I've heard a few times when using Routing in ASP.NET concerns whether you should use the default generic route pattern for most of your controllers, or whether you should specify individual routes for every action.
(this applies primarily to ASP.NET MVC today, but as routing is likely to be adopted more and more by Web Forms developers too it is a wider question)
The default route pattern looks something like this;
routeCollection.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home",
action = "Index",
id = string.Empty });
This means that as soon as I add a new controller to the project, every action is accessible using the default pattern. It also means that the Url pattern is the same for every controller.
After some consideration, and discussions with a few people (the talented Stuart Leeks, and the Web Client Guidance dev team spring to mind) I have to say I've come around to thinking that this is less than ideal.
The alternative is to delete this route definition... and immediately you are forced to add explicit routes for every controller or action that you add. This process makes you think about what Url scheme makes sense for any functionality you add.
A quick example
Let's take a look at some examples to see what this means in practice. I'm using the Web Client Guidance Reference Implementation from p&p – have a look under "~/Initialization/ShellRoutesSupplier.cs" and you'll see where the main application routes are configured. This is part of the Modularity infrastructure in the RI – I'm sure I'll blog about that at some point too, but you can read it in the WCG docs too.
The routes for Search in the RI are as follows;
routeCollection.MapRoute("Search",
"Search",
new { controller = "Search", action = "Results" });
routeCollection.MapRoute("SearchActions",
"Search/{action}/{id}",
new { controller = "Search", id = (string)null });
What this shows is that the Search controller's main action – "Results" – is accessed using Url patterns something like the following;
https://localhost/Search?query=a
I think this is a very natural Url scheme for a search results page. It is certainly more logical than
https://localhost/Search/Results?query=a
However, the very next route definition sets up a pattern that will match any other action on the Search controller. This is pretty much the equivalent of the default routing scheme. However, the development team have carefully considered the Search controller and decided to expose these actions to support JavaScript functionality. The fact that this consideration has been made is the important point – it isn't a bad thing to expose actions using nice general patterns as long as you've thought about it.
Another set of interesting routes in the RI is for user profiles;
routeCollection.MapRoute("ProfileEdit",
"Profile/Edit",
new { controller = "Profile", action = "Edit" });
routeCollection.MapRoute("ProfileView",
"Profile/{userName}",
new { controller = "Profile", action = "Show",
userName = (string)null });
This sets up a route to edit the current users profile, and a nice routing pattern for viewing your friend's profiles, something like this;
https://localhost/Profile/Simon or
https://localhost/Profile/SomeoneElse
You could also have a look at the Top Songs module (under "~/Areas/TopSongs/TopSongsRoutesSupplier.cs") for other examples.
Summary
The bottom line is that I really like this approach of deleting the default route, and considering each action or controller individually.
It enhances the user's experience of your site by providing logical, often short, and easy to follow routes to common functionality. It also contributes to providing a clear perceived structure to your site. It also gives me a feeling of more confidence that sensitive actions are secured and controller code that shouldn't be exposed accidentally isn't (even if this is a little unjustified, and you certainly shouldn't rely on that!).
The trade-off is that you must do this manually for every controller... but having done this in my own code I think this is negligible and well worthwhile.
What approach do you use?
Comments
Anonymous
February 01, 2010
As part of a project I started, I went sown the route of auto-generating these routes at runtime along with conventions in place. Unfortunately due to other things popping up I didn't complete it entirely. If you're interested ping me and I'll show you what I have do farAnonymous
February 01, 2010
@ Hadi; that's an interesting comment... I have to say I'm not sure I understand the benefit of autogenerating them over and above using some kind of generic pattern, or even your own route handler perhaps. Which makes what you say interesting! Any comments on why you chose that approach? SimonAnonymous
October 10, 2010
Interresting article. I only see one problem eg. with the routeCollection.MapRoute("ProfileView","Profile/{userName}"… when creating the solution one need to make sure at nobody is allowed to create a userName that equal ”Edit”. After putting the solution online and 20.000 users later. What if I want to add a profile route: routeCollection.MapRoute("ProfileClean", "Profile/Clean", new { controller = "Profile", action = "Clean" }); now I need to make clear that nobody has signed up with the userName ”Clean” and so on, isn't this kind of a problem? :-) /HenrikAnonymous
October 13, 2010
@ Henrik - funnilly enough I used that example to call out that common mistake... and then failed to highlight it! So thanks :) It can be worse though, if the routes were the other way round, as it'd mean registering a specific username could prevent access to other functionality for others. SimonAnonymous
October 19, 2010
@simon: I've thought a little more and come to: if you want the clean url's to profiles (http://localhost/Profile/Simon), a solution could be to let the crud operations have their own controller and route. In your example it would mean that ProfileEdit is moved to a new "ProfileAdm" controller routeCollection.MapRoute ("ProfileEdit", "ProfileAdm/Edit", new {controller = "ProfileAdm" action = "Edit"}); routeCollection.MapRoute ("profile view", "Profile / {username}", new {controller = "Profile" action = "Show", username = (string) null}); and "my" new Clean function will then also be added to ProfileAdm controller and not on the Profile controller ;-)