14

I'm able to successfully register a custom route handler (deriving from IRouteHandler) inside global.asax.cs for a Web API route ala:

        routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{client}/api/1.0/{controller}/{action}/{id}",
            defaults: new{id = UrlParameter.Optional}
        ).RouteHandler = new SingleActionAPIRouteHandler();

However I can't seem to find a way to do this when trying to setup an in memory host for the API for integration testing, when I call HttpConfiguration.Routes.MapRoute I'm not able to set a handler on the returned IHttpRoute.

Should I be doing this differently (for instance by using a custom HttpControllerSelector)? I'd obviously like to do it the same way in both cases.

Thanks, Matt

EDIT:

So what I ended up doing was basically following the advice from below, but simply overriding the HttpControllerDispatcher class as follows:

public class CustomHttpControllerDispatcher : HttpControllerDispatcher
{
    public CustomHttpControllerDispatcher(HttpConfiguration configuration) : base(configuration)
    {
    }
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // My stuff here

        return base.SendAsync(request, cancellationToken);
    }
}
mattcole
  • 1,229
  • 2
  • 12
  • 22

1 Answers1

13

You are very right. Self host returns IHttpRoute and takes HttpMessageHandler as a parameter. There seems no inbuilt way to specificity a route handler.

Update: To be a bit clearer

You should almost certainly have a go at using a HttpControllerSelector and implementing a custom one... An example being. http://netmvc.blogspot.co.uk/

What follows is a bit of experimentation if the HttpControllerSelector is not sufficent for your requirements for what ever reason...

But, as you can see the MapHttpRoute does have an overload for HttpMessageHandler so you can experiment with this... if the handler is NULL then it defaults to IHttpController but you can implement your own and use this to direct to the correct controller... The MVC framework appears to use [HttpControllerDispatcher] here, so borrowing some code you can place your own controller / route selection code here too - you have access to the route and selector and can swap it in and out yourself.

This CustomHttpControllerDispatcher code is for DEMO only... look for the line

//DO SOMETHING CUSTOM HERE TO PICK YOUR CONTROLLER

And perhaps have a play with that...

Example route:

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

Example CustomHttpControllerDispatcher:

public class CustomHttpControllerDispatcher : HttpMessageHandler
{
        private IHttpControllerSelector _controllerSelector;
        private readonly HttpConfiguration _configuration;

        public CustomHttpControllerDispatcher(HttpConfiguration configuration)
        {
            _configuration = configuration;
        }

        public HttpConfiguration Configuration
        {
            get { return _configuration; }
        }

        private IHttpControllerSelector ControllerSelector
        {
            get
            {
                if (_controllerSelector == null)
                {
                    _controllerSelector = _configuration.Services.GetHttpControllerSelector();
                }
                return _controllerSelector;
            }
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
                return SendAsyncInternal(request, cancellationToken);
        }

        private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
        {

            IHttpRouteData routeData = request.GetRouteData();
            Contract.Assert(routeData != null);

            //DO SOMETHING CUSTOM HERE TO PICK YOUR CONTROLLER
            HttpControllerDescriptor httpControllerDescriptor = ControllerSelector.SelectController(request);
            IHttpController httpController = httpControllerDescriptor.CreateController(request);

            // Create context
            HttpControllerContext controllerContext = new HttpControllerContext(_configuration, routeData, request);
            controllerContext.Controller = httpController;
            controllerContext.ControllerDescriptor = httpControllerDescriptor;

            return httpController.ExecuteAsync(controllerContext, cancellationToken);
        }
}
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
  • Thanks very much Mark, I'll admit to being quite new to the internals of the WebAPI framework, could you explain why this approach is better than supplying a custom IHttpControllerSelector to the configuration? It looks like the default HttpControllerDispatcher uses the ControllerSelector defined in the configuration to take care of the work that I'm looking to do. – mattcole Sep 14 '12 at 13:15
  • @mattcole I should have been a bit clearer sorry - I wanted to confirm you are correct and that I suspect (as you already did) that you should have a look at a custom HttpControllerSelector http://netmvc.blogspot.co.uk/ as the preference if all you are doing is forwarding to a controller. I was only thinking that maybe you you were doing more than picking controllers and perhaps wanted to use other handlers other than controller etc... The CustomHttpControllerDispatcher idea was a bit of a plaything and experiment... sorry for not being clearer. – Mark Jones Sep 14 '12 at 13:36
  • No problem Mark, in the end I went with a version of what you suggested, as I was effectively only changing some values in the routevalues I just overrode the SendAsync method, did my work and then let the pipeline continue on its way. – mattcole Sep 14 '12 at 14:12
  • how to stop process and let it fall in secondaries? also i do namespace checking, if the 2 matching controller are exists that are in same list of namespaces, then its conflict, otherwise it's ok, and i only want to take the controller from that specific namespace – Hassan Faghihi Dec 25 '16 at 08:58