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.