Tuesday, April 13, 2010

ASP.NET MVC Domain Routing and areas

Wow, it's been a while since I've posted! I've got a good one (at least I think so) for this post. I've been messing around with ASP.Net MVC over the last few weeks and really love the control and simplicity of it all. So naturally I would like to re-create some existing websites I manage using MVC. The problem is I have a shared hosting account with GoDaddy so I need to host all sites with one account and route differently depending on which domain is in the url. I thought of using the new areas support in MVC2 but did not like the idea of having the area name in the url. My ideal solution would route 'www.test.com' to the 'test' area and 'www.test2.com' to the 'test2' area without having 'test' or 'test2' show up in the url.
First I found this post that describes how to create a DomainRoute class that will allow you to include the domain in the route. Pretty cool. The next step was to create some methods that allow me to include the domain when mapping a route in the area registration. I created two extension methods for that.
   10 public static class DomainRouteExtensions
   11 {
   12     public static Route MapRoute(this RouteCollection routes, string name, string domain, string url, object defaults, object constraints, string[] namespaces)
   13     {
   14         if (routes == null)
   15         {
   16             throw new ArgumentNullException("routes");
   17         }
   18         if (url == null)
   19         {
   20             throw new ArgumentNullException("url");
   21         }
   22 
   23         DomainRoute route = new DomainRoute(domain, url, defaults, new MvcRouteHandler())
   24         {
   25             Defaults = new RouteValueDictionary(defaults),
   26             Constraints = new RouteValueDictionary(constraints),
   27             DataTokens = new RouteValueDictionary()
   28         };
   29 
   30         if ((namespaces != null) && (namespaces.Length > 0))
   31         {
   32             route.DataTokens["Namespaces"] = namespaces;
   33         }
   34 
   35         routes.Add(name, route);
   36 
   37         return route;
   38     }
   39 
   40     public static Route MapRoute(this AreaRegistrationContext context, string name, string domain, string url, object defaults, object constraints, string[] namespaces)
   41     {
   42         if (namespaces == null && context.Namespaces != null)
   43         {
   44             namespaces = context.Namespaces.ToArray();
   45         }
   46 
   47         Route route = context.Routes.MapRoute(name, domain, url, defaults, constraints, namespaces);
   48         route.DataTokens["area"] = context.AreaName;
   49 
   50         // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
   51         // controllers belonging to other areas
   52         bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
   53         route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
   54 
   55         return route;
   56     }
   57 }
Basically, these two extension methods are just slight modifications to the existing MVC methods that allow me to include a domain string and replace the standard Route object with a DomainRoute object. After a little debuging, this worked well with one small change to the DomainRoute.GetRouteData method by adding the DataTokens to the returned data object
    1     foreach (var item in this.DataTokens)
    2     {
    3         data.DataTokens.Add(item.Key, item.Value);
    4     }
    5 }
    6 
    7 return data;
Now that that is all done, all that is left is to add a route in the area registration.
   19 public override void RegisterArea(AreaRegistrationContext context)
   20 {
   21     context.MapRoute(
   22         "KMI_Default",
   23         "test.kmi.com",
   24         "{controller}/{action}/{id}",
   25         new { controller = "Home", action = "Index", id = UrlParameter.Optional },
   26         null,
   27         null);
   28 }
And thats it. The two nulls are needed to make sure the correct overload is called and the domain string is not mapped to the constraints since it is of type object. Do this will a couple more areas and multiple domains mapped to the same root folder in IIS can route to different areas in the MVC application. A little more work needs to be done to make it a complete solution. As it stands right now, the DomainRoute class does not support constraints.