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?
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.