Search code examples
asp.net-mvcasp.net-web-apiasp.net-web-api2asp.net-web-api-routingmessage-handlers

Route-specific ASP.NET MVC Web API message handler not invoking controller action


I've got an ASP.NET MVC Web API controller:

[HttpPost]
public async Task<HttpResponseMessage> Post(HttpRequestMessage req, CancellationToken cancellationToken) {...}

And a custom message handler I created:

public class MyMessageHandler : DelegatingHandler
{
     protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
     {
         // ...

         var response = await base.SendAsync(request, cancellationToken);

         // ...

        return response;
      }
  }

And in WebConfigApi.cs I wire up the message handler route-specific to the controller action method:

configuration.Routes.MapHttpRoute(
   name: "UpdateStuffAPI",
   routeTemplate: "api/updatestuff/post/{stuffid}",
   defaults: new { feedid = RouteParameter.Optional },
   constraints: null,
   handler: new MyMessageHandler()
);

When I POST to the controller action method, e.g.:

http://hostname/api/updatestuff/post?stuffid=12345

The message handler intercepts the request as expected. But in stepping through the line:

var response = await base.SendAsync(request, cancellationToken);

the controller action method is never hit.

As a test I removed the route-specific wiring and made the message handler global:

configuration.MessageHandlers.Add(new MyMessageHandler());

and SendAsync properly invokes my controller's action method.

So my thought was that something is wrong with the route definition. However, the message handler is invoked with the route-specific wiring, and, Route Debugger shows that when I POST to my controller (http://hostname/api/updatestuff/post?stuffid=12345), that that route is being used.

Why isn't my action method being invoked when I wire up the message handler in a route-specific way?


Solution

  • I was missing the code that ties the message handler back to the route/controller it should invoke next.

    Route-specific message handlers must be specifically told about the Web Api application's HttpConfiguration. What I had in WebConfigApi.cs was:

    configuration.Routes.MapHttpRoute(
       name: "UpdateStuffAPI",
       routeTemplate: "api/updatestuff/post/{stuffid}",
       defaults: new { feedid = RouteParameter.Optional },
       constraints: null,
       handler: new MyMessageHandler()
    );
    

    What I needed to was:

    configuration.Routes.MapHttpRoute(
       name: "UpdateStuffAPI",
       routeTemplate: "api/updatestuff/post/{stuffid}",
       defaults: new { feedid = RouteParameter.Optional },
       constraints: null,
       handler: new MyMessageHandler(configuration)
    );
    

    In other words, the configuration object needed to be passed to the message handler upon construction. So the message handler needs a constructor:

    public MyMessageHandler(HttpConfiguration httpConfiguration)
    {
       InnerHandler = new HttpControllerDispatcher(httpConfiguration);
    }
    

    I naively assumed that setting handler: new MyMessageHandler() in the route map was enough to tie the message handler back to the controller(s) that the route maps to.

    While this is resolved, I admittedly don't yet understand why this is required (why my assumption was incorrect) so I'm going to read up on that.