13

I have a .NET project running in C# using WebApi 2.2.

I am registering all of my routes using attributes. What I would like to do is pro grammatically retrieve all of the attribute route templates as strings.

Something like: var routeTemplates = System.Web.Routing.RouteTable.Routes.Select(x => x.RouteTemplates);

I am able to see all of the the routes when I put a watch on ControllerContext.Configuration.Routes

However, I cannot seem to access the routes from my code as they are protected internals. How do I get at them?

I've attached a screen shot which shows the values I am seeing on my locals watch that I need to get at.

The data I want to get at

Ray Suelzer
  • 4,026
  • 5
  • 39
  • 55
  • Apparently stackoverflow doesn't let you view the full size images. So it is uploaded here: http://imgur.com/DFlMqDa – Ray Suelzer Mar 30 '15 at 01:28

1 Answers1

12

When registering the attribute routes in Web API you can register a custom IDirectRouteProvider to customize how attribute routes are found. In that custom IDirectRouteProvider you can delegate all the "hard" work to the default implementation, DefaultDirectRouteProvider, that looks at all the controllers and actions to compute the list of attribute routes, and then take credit for all that hard work.

To set this all up, first create a new "observable" direct route provider that delegates all its work:

public class ObservableDirectRouteProvider : IDirectRouteProvider
{
    public IReadOnlyList<RouteEntry> DirectRoutes { get; private set; }

    public IReadOnlyList<RouteEntry> GetDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList<HttpActionDescriptor> actionDescriptors, IInlineConstraintResolver constraintResolver)
    {
        var realDirectRouteProvider = new DefaultDirectRouteProvider();
        var directRoutes = realDirectRouteProvider.GetDirectRoutes(controllerDescriptor, actionDescriptors, constraintResolver);
        // Store the routes in a property so that they can be retrieved later
        DirectRoutes = DirectRoutes?.Union(directRoutes).ToList() ?? directRoutes;
        return directRoutes;
    }
}

And then use this new class from the WebApiConfig.Register method in your app's startup:

public static class WebApiConfig
{
    public static ObservableDirectRouteProvider GlobalObservableDirectRouteProvider = new ObservableDirectRouteProvider();

    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes(GlobalObservableDirectRouteProvider);

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Note that the data is ultimately stored in a static field. This is required because the code inside WebApiConfig.Register is not called immediately - it's called later in global.asax.cs. So, to observe the results of everything, add some code to Application_Start:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    var allDirectRoutes = WebApiConfig.GlobalObservableDirectRouteProvider.DirectRoutes;
    // now do something with 'allDirectRoutes'
}

And in a little test I wrote, I got these values:

enter image description here

And there you have it, a list of all the attribute routes in the app!

Note: There's additional data squirrelled away in the DataTokens property of each route if you want to figure out where each attribute route came from.

Vitaly Ascheulov
  • 182
  • 2
  • 15
Eilon
  • 25,582
  • 3
  • 84
  • 102
  • Thank you! I'll play around with this, but seems like the right way to go. – Ray Suelzer Apr 04 '15 at 01:54
  • I have to wait 23 hours to award the bounty. – Ray Suelzer Apr 04 '15 at 01:55
  • Also found this https://github.com/Daniel15/RouteJs/blob/master/src/RouteJs/RouteCollectionUtils.cs – Ray Suelzer Apr 04 '15 at 04:07
  • @RaySuelzer it seems that code is using private reflection, and so would not be supported. The code I showed is doing things the 100% public supported way. – Eilon Apr 04 '15 at 18:04
  • yep. your solution is fantastic. – Ray Suelzer Apr 04 '15 at 18:14
  • The only thing that needed to be changed was this: ` DirectRoutes = DirectRoutes.Union(directRoutes);` – Ray Suelzer Apr 04 '15 at 18:25
  • @RaySuelzer I don't think that should be needed because `DirectRoutes` starts out null. Or are you seeing it called more than once? (In which case your code is correct.) – Eilon Apr 04 '15 at 23:24
  • Yep. it's called for every controller, so I just put them all together. – Ray Suelzer Apr 05 '15 at 23:48
  • @RaySuelzer ah indeed, I updated the code in my answer to reflect that. Thanks! – Eilon Apr 06 '15 at 15:57
  • 1
    For anyone interested in using this for discovering all the webapi routes, be aware that this method will not return the routes registered via MapHttpRoute. – MoonStom Oct 05 '15 at 18:20
  • 2
    with this solution is there a nice way to get the acceptable verb for each route as well? – Adam Stewart Dec 30 '15 at 08:30
  • Be aware that this code is not test safe. Using this code in a test environment where it is spun up multiple times in quick succession will cause DirectRoutes to grow and grow and grow – Smilie May 08 '18 at 06:27