Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An approach to refactor ASP.NET MVC Routes

0.00/5 (No votes)
11 Feb 2013 1  
This article highlights an approach to organize ASP.NET MVC routes in an organized manner

Introduction

This article suggests an approach to organize ASP.NET MVC Routes and re-factor MapRoute method.

Background  

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.

// 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 default
      );
  }
} 


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.

This article is all about one such approach to organize your Routes.

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)
  •    |_ LoginRoutes.cs 
  •    |_ UserRoutes.cs 
  •    |_ ProductRoutes.cs
  •    |_ SignleSignOnRoutes.cs 
  •    |_ OrderRoutes  


Makes Sense? I guess it is pretty neat than adding all Routes in RegisterRoutes.  

Implementing Organized Routes   

There are 3 components to suggested solution:  

  1. IRouting interface: This will define one method "RegisterRoutes", that all Routing file will use to register their routes
  2. Routes: Individual class files that implement IRouting and define routes 
  3. RoutesManager: Instead of explicitly calling RegisterRoutes of every class implementing IRouting, Route Manager will auto-load all routes by finding classes that implement IRouting.  
Let's cover each:

IRouting Interface   

 public interface IRouting
   {
       void RegisterRoutes(RouteCollection routes);
   }

IRouting 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.  

Sample Implementations   

LoginRoutes.cs  

 public class LoginRoutes : IRouting
{
  public void RegisterRoutes(RouteCollection routes)
  {
    routes.MapRoute("loginroute", "login", new { controller = "Login", action = "Index" });
    routes.MapRoute<logincontroller>("Register" x=>x.Register());*
  }
}

OrderRoutes.cs 

public class OrderRoutes : IRouting
{
  public void RegisterRoutes(RouteCollection routes)
  {
    routes.MapRoute("buy", "placeorder/{id}", new { controller = "Orders", action = "Buy" });
    routes.MapRoute("cart", "cart", new { controller = "Orders", action = "ViewCart" });   
    //More intuitive and VS intellisense supportive way to define routes 
    routes.MapRoute<OrderController>("cart/payment",r=>r.MakePayment());
  <span style="font-size: 9pt;">}
</span>}
 <span style="font-size: 9pt;"> </span>

Routes Manager 

public class RoutesManager
{
  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
   routings.ForEach(r=>((IRouting) Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName,r.FullName).Unwrap()).RegisterRoutes(routes));
   //You can also use DI
   //routings.ForEach(r => ((IRouting)MvcApplication.UnityContainer.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 registers 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  

Cool! We've a pretty organized and automated mechanism to add routes now. As a quick tip, we can extend MapRoute method to support much more intuitive and Visual Supported way for adding Routes.

Extending Map Routes  

Default way to add Routes is:

 routes.MapRoute(
                "A_Unique_Key",
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Login", action = "Index", id = UrlParameter.Optional } 
            );

When you're adding a lot of routes, there're a few things to dislike in this method: 

1. You may mistakenly add same unique keys in 2 routes to identify later at runtime (which is irritating)

2. Controllers and Actions has no intellisense support, may be mistakenly typed and if someone changes controller or action name, you may miss to update routes. 

As a quick tip, you may create an extension method to ease routes mapping:  

  public static void MapRoute<T>(this RouteCollection routes, string key="", string path = "", Expression<Func<T, ActionResult>> actionMethod = null, object defaultValues = null, object constraints = null, string[] namespaces = null) where T : IController
        {
            var controller = typeof(T).Name.Replace("Controller", "");
 
            routes.MapRoute(string.IsNullOrEmpty(key)?Guid.NewGuid().ToString():key, path, new { controller = controller, action = (actionMethod == null ? "Index" : ((MethodCallExpression)actionMethod.Body).Method.Name) }, constraints, namespaces);
        }  
1. This extension method automatically sets action to "Index" if no action method is specified
2. Specify controller as T and action with intellisense
3. This method automatically assigns a new Guid for key. For routes, you do not expect to use RouteLink, you can skip setting key directly to avoid any duplicate key  issue.

Usage  Examples 

1. This sets LoginController's Index() method to handle default url (http://somesite.com/)  

routes.MapRoute<LoginController>("key");   

2. This sets LoginController's Register() method to url "http://somesite.com/register" 

routes.MapRoute<LoginController>("key","Register",x=>x.Register());   

This was just a quick Tip. You can be creative to make more useful extensions.

Hope you liked my 1st article here. Smile | <img src=

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here