RSS 2.0
# Friday, January 01, 2010

Kazi Manzur Rashid has a post about registering Areas dynamically after the registration of other routes and the problems this has since the order the routes are registered is very important. Go read his post and come back for a possible solution to the problem.

Ok, based on his post I decided to try to implement exactly what he is looking for. After poking around with reflector and brushing up my Reflection skills I came up with a first implementation that does the trick.

  1:     public static class RoutCollectionExtension
  2:     {
  3:         public static RouteCollection AddArea(this RouteCollection routes, string routeName, Route newRoute)
  4:         {
  5:             var fieldInfo =  routes.GetType()
  6:                 .GetField("_namedMap", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  7: 
  8:             var dict = fieldInfo.GetValue(routes);
  9:             dict.GetType()
 10:                 .GetMethod("Add", BindingFlags.Public | BindingFlags.Instance)
 11:                 .Invoke(dict,new object[]{routeName,newRoute});
 12: 
 13:             fieldInfo.SetValue(routes,dict);
 14: 
 15:             routes.GetType()
 16:                 .GetMethod("InsertItem", BindingFlags.NonPublic | BindingFlags.Instance)
 17:                 .Invoke(routes, new object[] { 0, newRoute });
 18: 
 19:             return routes;
 20:         }
 21:     }

After posted this solution as a comment on Kazi’s post I decided to polish this a little more and to actually provide a similar API as the MapRoute extension from the MVC framework. The idea is to provide a set of InsertRoute and InsertRouteAfter.

So for the InsertRoute, this is the final code:

  1:     public static class RoutCollectionExtension
  2:     {
  3:         public static void InsertRoute(this RouteCollection routes, int index, string routeName, Route newRoute)
  4:         {
  5:             var fieldInfo =  routes.GetType()
  6:                 .GetField("_namedMap", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  7: 
  8:             var dict = fieldInfo.GetValue(routes);
  9:             dict.GetType()
 10:                 .GetMethod("Add", BindingFlags.Public | BindingFlags.Instance)
 11:                 .Invoke(dict,new object[]{routeName,newRoute});
 12: 
 13:             fieldInfo.SetValue(routes,dict);
 14: 
 15:             routes.GetType()
 16:                 .GetMethod("InsertItem", BindingFlags.NonPublic | BindingFlags.Instance)
 17:                 .Invoke(routes, new object[] { index, newRoute });
 18:         }
 19: 
 20: 
 21:         public static Route InsertRoute(this RouteCollection routes, int index, string name, string url)
 22:         {
 23:             return routes.InsertRoute(index, name, url, null, null);
 24:         }
 25: 
 26:         public static Route InsertRoute(this RouteCollection routes, int index, string name, string url, object defaults)
 27:         {
 28:             return routes.InsertRoute(index, name, url, defaults, null);
 29:         }
 30: 
 31:         public static Route InsertRoute(this RouteCollection routes, int index, string name, string url, string[] namespaces)
 32:         {
 33:             return routes.InsertRoute(index, name, url, null, null, namespaces);
 34:         }
 35: 

36: public static Route InsertRoute(this RouteCollection routes, int index, string name, string url,

object defaults, object constraints)

 37:         {
 38:             return routes.InsertRoute(index, name, url, defaults, constraints, null);
 39:         }
 40: 

41: public static Route InsertRoute(this RouteCollection routes, int index, string name, string url,

object defaults, string[] namespaces)

 42:         {
 43:             return routes.InsertRoute(index, name, url, defaults, null, namespaces);
 44:         }
 45: 

46: public static Route InsertRoute(this RouteCollection routes, int index, string name, string url, object defaults,

object constraints, string[] namespaces)

 47:         {
 48:             if (routes == null)
 49:             {
 50:                 throw new ArgumentNullException("routes");
 51:             }
 52:             if (url == null)
 53:             {
 54:                 throw new ArgumentNullException("url");
 55:             }
 56:             var item = new Route(url, new MvcRouteHandler())
 57:                              {
 58:                                  Defaults = new RouteValueDictionary(defaults),
 59:                                  Constraints = new RouteValueDictionary(constraints),
 60:                                  DataTokens = new RouteValueDictionary()
 61:                              };
 62:             if ((namespaces != null) && (namespaces.Length > 0))
 63:             {
 64:                 item.DataTokens["Namespaces"] = namespaces;
 65:             }
 66:             routes.InsertRoute(index, name, item);
 67:             return item;
 68:         }
 69:     }

The problem with this is that you probably don’t know the index of the routes and those index will change with each route that get’s registered. So InsertRouteAfter is better since we can insert a route after another route by name. The code is very simple, I won’t display all the overloads just the actual implementation.

  1:         public static void InsertRouteAfter(this RouteCollection routes, string nameOfExistingRoute, string nameOfRouteToInsert, Route newRoute)
  2:         {
  3:             var fieldInfo = routes.GetType()
  4:                 .GetField("_namedMap", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  5: 
  6:             var dict = fieldInfo.GetValue(routes);
  7:             dict.GetType()
  8:                 .GetMethod("Add", BindingFlags.Public | BindingFlags.Instance)
  9:                 .Invoke(dict, new object[] { nameOfRouteToInsert, newRoute });
 10: 
 11:             var existingRoute = dict.GetType()
 12:                 .GetProperty("Item")
 13:                 .GetValue(dict, new[] {nameOfExistingRoute});
 14: 
 15:             fieldInfo.SetValue(routes, dict);
 16: 
 17:             var index = routes.GetType()
 18:                 .GetMethod("IndexOf", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
 19:                 .Invoke(routes, new[] {existingRoute});
 20: 
 21:             index = (int) index + 1;
 22: 
 23:             routes.GetType()
 24:                 .GetMethod("InsertItem", BindingFlags.NonPublic | BindingFlags.Instance)
 25:                 .Invoke(routes, new[] { index, newRoute });
 26:         }
Warning!!!

If you decide to use this code make sure that you have tests in place since we are relying in things like the name of a internal field that can be changed without affecting the public API so this extensions are fragile from that point of view. Besides that, reflection is slow, but since route registration should happens only once per application I’m not worry about that part.

 

Shout it
kick it on DotNetKicks.com Friday, January 01, 2010 10:47:18 PM (Eastern Standard Time, UTC-05:00) by Hernan Garcia #    Comments [0] - Trackback
Programming
Add The Dynamic Programmer Mippin widget
Navigation
Archive
<January 2010>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456
What I'm reading
Shelfari: Book reviews on your book blog
About the author/Disclaimer
Hernan Garcia I have been a software developer for the last 16 years or so.
I was a journalist before and I still consider myself one.
Besides baseball, programming is my other big passion.

Me on twitter. @TheProgrammer
Certified Scrum Master
© Copyright 2010
Hernan Garcia
Sign In
Statistics
Total Posts: 197
This Year: 15
This Month: 1
This Week: 0
Comments: 70
Themes
Pick a theme:
All Content © 2010, Hernan Garcia