8

I need a custom IHttpControllerSelector which should be applied to a specific route only. All other web api routes should use the default implementation of IHttpControllerSelector.

While researching I found the following code that is meant to replace the IHttpControllerSelector at application start, but it replaces the default controller selector completely, which causes that all routes in the application use my custom controller selector:

config.Services.Replace(typeof(IHttpControllerSelector), 
                                                    new CustomControllerSelector(config));

Is there a way to configure the IHttpControllerSelector for a single route?

Liam
  • 22,818
  • 25
  • 93
  • 157
Raphael Müller
  • 951
  • 2
  • 8
  • 20

1 Answers1

14

You can assign a per-route message handler to the route that needs to use a different controller selection logic. This handler would mark the HttpRequestMessage with a flag that this request needs to be treated differently.

Then simply make the CustomControllerSelector inherit from DefaultHttpControllerSelector and inspect that flag:

  • if it's set, continue with your custom logic
  • if it's not set, return to base (DefaultHttpControllerSelector)

Here is the code:

1) message handler, setting the flag

public class RouteSpecificHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Properties["UseCustomSelector"] = true;
        return base.SendAsync(request, cancellationToken);
    }
}

2) assigning per route message handler to the specific route only (do not run for other routes)

        config.Routes.MapHttpRoute(
            name: "MyRoute",
            routeTemplate: "api/dummy/{id}",
            defaults: new {controller = "Dummy", id = RouteParameter.Optional},
            constraints: null,
            handler: new RouteSpecificHandler { InnerHandler = new HttpControllerDispatcher(config) }
            );

3) custom selector respecting the flag:

public class CustomSelector : DefaultHttpControllerSelector
{
    public CustomSelector(HttpConfiguration configuration) : base(configuration)
    {
    }

    public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        if (request.Properties.ContainsKey("UseCustomSelector") &&
            request.Properties["UseCustomSelector"] as bool? == true)
        {
            //your logic goes here
        }

        return base.SelectController(request);
    }
}

4) registering the selector:

config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config));

edit

If you wish to not inherit from DefaultHttpControllerSelector - then implement IHttpControllerSelector directly, and instead of calling the base.SelectController(request) save the old selector as a field/property in your class

public class CustomSelector : IHttpControllerSelector
{
    private HttpConfiguration _config;

    public IHttpControllerSelector PreviousSelector {get; set;}

    public CustomSelector(HttpConfiguration configuration)
    {
         _config = configuration;
    }

    public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        if (request.Properties.ContainsKey("UseCustomSelector") &&
            request.Properties["UseCustomSelector"] as bool? == true)
        {
            //your logic goes here
        }

        return PreviousSelector.SelectController(request);
    }
}

Then just change the registration:

  var previousSelector = config.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
 config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config) { PreviousSelector = previousSelector});  
Filip W
  • 26,577
  • 6
  • 89
  • 80
  • Thank you for the well-explained answer. This looks promising, but what if I don't know if the previous registered `IHttpControllerSelector` was the `DefaultHttpControllerSelector`? I am working on an extension for a CMS, where other extensions might also override the `IHttpControllerSelector`. Assuming that my extension is the second one overriding it, the first extension's `IHttpControllerSelector` won't be used anymore. – Raphael Müller Sep 25 '14 at 14:29
  • see the edit for an answer. Tip - you should have included this information in the original question. – Filip W Sep 26 '14 at 12:20
  • Great, thank you again! I thought that this information was not relevant when I wrote my question, but yes I should have included it. – Raphael Müller Sep 30 '14 at 12:20
  • It would be interesting to see how this is done with .net core. – Phil Cooper Dec 15 '16 at 16:58
  • Great answer @FilipW, Any way to achieve this method in Asp.Net Core? IHttpControllerSelector is not available there. – MaorB Nov 26 '19 at 14:18