22{
33 using System . Collections . Generic ;
44 using System . Diagnostics . Contracts ;
5+ using System . Reflection ;
56 using System . Web . Http . Routing ;
7+ using static System . Reflection . BindingFlags ;
68
79 static class HttpRouteCollectionExtensions
810 {
@@ -11,9 +13,7 @@ internal static string GetRouteName( this HttpRouteCollection routes, IHttpRoute
1113 Contract . Requires ( routes != null ) ;
1214 Contract . Requires ( route != null ) ;
1315
14- var items = new KeyValuePair < string , IHttpRoute > [ routes . Count ] ;
15-
16- routes . CopyTo ( items , 0 ) ;
16+ var items = CopyRouteEntries ( routes ) ;
1717
1818 foreach ( var item in items )
1919 {
@@ -25,5 +25,61 @@ internal static string GetRouteName( this HttpRouteCollection routes, IHttpRoute
2525
2626 return null ;
2727 }
28+
29+ static KeyValuePair < string , IHttpRoute > [ ] CopyRouteEntries ( HttpRouteCollection routes )
30+ {
31+ Contract . Requires ( routes != null ) ;
32+ Contract . Ensures ( Contract . Result < KeyValuePair < string , IHttpRoute > [ ] > ( ) != null ) ;
33+
34+ var items = new KeyValuePair < string , IHttpRoute > [ routes . Count ] ;
35+
36+ try
37+ {
38+ routes . CopyTo ( items , 0 ) ;
39+ }
40+ catch ( NotSupportedException ) when ( routes . GetType ( ) . FullName == "System.Web.Http.WebHost.Routing.HostedHttpRouteCollection" )
41+ {
42+ var keys = GetRouteKeys ( routes ) ;
43+
44+ for ( var i = 0 ; i < keys . Count ; i ++ )
45+ {
46+ var key = keys [ i ] ;
47+ var route = routes [ key ] ;
48+
49+ items [ i ] = new KeyValuePair < string , IHttpRoute > ( key , route ) ;
50+ }
51+ }
52+
53+ return items ;
54+ }
55+
56+ static IReadOnlyList < string > GetRouteKeys ( HttpRouteCollection routes )
57+ {
58+ Contract . Requires ( routes != null ) ;
59+ Contract . Ensures ( Contract . Result < IReadOnlyList < string > > ( ) != null ) ;
60+
61+ var collection = GetKeys ( routes ) ;
62+ var keys = new string [ collection . Count ] ;
63+
64+ collection . CopyTo ( keys , 0 ) ;
65+
66+ return keys ;
67+ }
68+
69+ static ICollection < string > GetKeys ( HttpRouteCollection routes )
70+ {
71+ Contract . Requires ( routes != null ) ;
72+ Contract . Ensures ( Contract . Result < ICollection < string > > ( ) != null ) ;
73+
74+ // HACK: System.Web.Routing.RouteCollection doesn't expose the names associated with registered routes. The
75+ // HostedHttpRouteCollection could have provided an adapter to support it, but didn't. Instead, it always throws
76+ // NotSupportedException for the HttpRouteCollection.CopyTo method. This only happens when hosted on IIS. The
77+ // only way to get the keys is use reflection to poke at the underlying dictionary.
78+ var routeCollection = routes . GetType ( ) . GetField ( "_routeCollection" , Instance | NonPublic ) . GetValue ( routes ) ;
79+ var dictionary = routeCollection . GetType ( ) . GetField ( "_namedMap" , Instance | NonPublic ) . GetValue ( routeCollection ) ;
80+ var keys = ( ICollection < string > ) dictionary . GetType ( ) . GetRuntimeProperty ( "Keys" ) . GetValue ( dictionary , null ) ;
81+
82+ return keys ;
83+ }
2884 }
2985}
0 commit comments