Refactoring ASP.NET MVC Routes
Routing is vital to MVC. Routing defines mappings between URL and ActionMethod - that would handle request to that URL. For example, following route mapping defines that when a user hits "http://mysite/shopping/cart", call OrderController's ShowCart() method to handle this request.
Said that "Routes should be defined explicitly", over a period of time your routes may grow significantly. It would be wise to re-factor and organize your routes such that they are manageable and in some way logically grouped so that it's pretty easy to locate a Route.
*If you liked this style of defining Routes, which is much for intuitive and Visual Studio intellisense supported, you may wish to check my routes extension post here.
Benefits of this approach:
routes.MapRoute("cart", "shopping/cart", new { controller = "Order", action = "ShowCart" });
Placing Routes
A common place to put route mappings is in RegisterGlobalFilters method inside Global.asax:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//Define Routes
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Login", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
}
Often, you would use generic route "Controller/Action/Parameters" (as in aforesaid code) which maps Controller/Action/parameter with URLs tightly. But I prefer to explicitly define all routes (or at least as many as possible) to decouple URL with their handler methods for several reasons listed below:
- No matter how good your architecture is, you would find a need to re-factor it, when it grows. With Routes tightly mapped to methods, you may find difficulty in re-factoring. (without altering URLs)
- You may find a need to change routes for indexing purpose on search-engines or for any other reason as demanded by business people (without altering actions)
- In a complex scenario, you may wish to provide options to marketing team to be able to change routes and define a mechanism to map it with methods (without altering actions)
- You may wish to change URL which is attacked (without altering actions)
- Or any other reason for which you wish to alter either of URL or Method without altering other
Said that "Routes should be defined explicitly", over a period of time your routes may grow significantly. It would be wise to re-factor and organize your routes such that they are manageable and in some way logically grouped so that it's pretty easy to locate a Route.
Idea is, to create a Folder called "Routes" and define individual .cs files, that logically segregate Routes. For example, you might have a following structure:
Web Solution
|_ Routes [Folder]
|_RoutesManager.cs (More on this later)
|_RoutesManager.cs (More on this later)
|_ LoginRoutes.cs
|_ UserRoutes.cs
|_ ProductRoutes.cs
|_ SignleSignOnRoutes.cs
|_ OrderRoutes
|_ SignleSignOnRoutes.cs
|_ OrderRoutes
Makes Sense? I guess it is pretty neat than adding all Routes in RegisterRoutes.
Implementation of Organized Routes
There are 3 components to suggested solution:- IRouting interface: This will define one method "RegisterRoutes", that all Routing file will use to register their routes
- Routes: Individual class files that implement IRouting and define routes
- RoutesManager: Instead of calling RegisterRoutes of every Route class defined in 2., Route Manager will auto load all routes by finding classes that implement IRouting.
IRouting Interface
public interface IRoutingIRouting defines RegisterRoutes which accepts RouteCollection as argument. This RouteCollection will be the global Routes collection, MVC framework uses and each class implementing IRouting will add their routes to this RouteCollection.
{
void RegisterRoutes(RouteCollection routes);
}
Sample Implementation
LoginRoutes.cs
OrderRoutes.cs
public class LoginRoutes : IRouting
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("loginroute", "login", new { controller = "Login", action = "Index" });
routes.MapRoute("Register" x=>x.Register());*
}
}
public class OrderRoutes : IRoutingAnd so on..
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("buy", "placeorder/{id}", new { controller = "Orders", action = "Buy" });
routes.MapRoute("cart", "cart", new { controller = "Orders", action = "ViewCart" });
routes.MapRoute("cart/payment", a=>a.MakePayment());*
}
}
*If you liked this style of defining Routes, which is much for intuitive and Visual Studio intellisense supported, you may wish to check my routes extension post here.
Routes Manager
Create a new file RoutesManager.cs in Routes folder and add following code to it:
public partial class Routes
{
public static void RegisterRoutes(RouteCollection routes)
{
//Find all Classes implemeting IRouting
var routings = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(x => typeof(IRouting).IsAssignableFrom(x)
&& x.IsClass).ToList();
//Call Register Routes
((IRouting) Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName,r.FullName).Unwrap()).RegisterRoutes(routes));
//You can also use DI
//routings.ForEach(r => ((IRouting)MvcApplication.Container.Resolve(r)).RegisterRoutes(routes));
}
}
We've written a generic method which search the assembly for all classes that implement IRouting and recursively call RegisterRoutes() for each such implementation.
Benefits of this approach:
- You can put your routes in an external library/assembly and modify first line to check that assembly. With this, you can easily add/update routes in other library, replace DLL and just restart app pool to have all routes updated
- 2nd line gives you an option to do dependency injection in your route class. For example, you may wish to read routes from a database or an XML file based on DI and then register those routes.
- You're free from possible mistake of creating a route but forgetting to register it as registration of routes here is seamless. It can find Routes from anywhere you define them
Comments
Post a Comment