Related question: Web API ApiController PUT and POST methods receive null parameters intermittently
Background
While load testing an existing Web API project I noticed a lot of null reference exceptions as a result of a parameter being null when posting to an action.
The cause seems to be a custom message handler registered to log requests while running in dev environments. Removing this handler resolves the issue.
I understand that in Web API I can only read the request body once and that reading it would always cause my parameter to be null as model binding wouldn't be able to take place. For that reason I'm using the ReadAsStringAsync() method with ContinueWith to read the body. It looks like this is behaving oddly in ~0.2% of requests (during local debugging using Apache Bench).
Code
At the most basic level I have the following:
Model
public class User
{
public string Name { get; set; }
}
API Controller
public class UsersController : ApiController
{
[HttpPost]
public void Foo(User user)
{
if (user == null)
{
throw new NullReferenceException();
}
}
}
Message Handler
public class TestMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content.ReadAsStringAsync().ContinueWith((task) =>
{
/* do stuff with task.Result */
});
return base.SendAsync(request, cancellationToken);
}
}
...which is registered during app start
GlobalConfiguration.Configuration.MessageHandlers.Add(new TestMessageHandler());
I'm using WebAPI 4.0.30506.0, the latest at the time of posting. All other MS packages in the project are also running the latest version (demo project linked below now updated to reflect this).
Testing
The initial testing was made using Loadster running against a load-balanced IIS 7.5 setup on Server 2008 R2 with .NET 4.0.30319. I'm replicating this locally on IIS 7.5 on Windows 7 with .NET 4.5.50709 using Apache Bench.
ab -n 500 -c 25 -p testdata.post -T "application/json" http://localhost/ModelBindingFail/api/users/foo
where testdata.post contains
{ "Name":"James" }
With this testing I'm seeing roughly 1 failure for the 500 requests, so ~0.2%.
Next steps...
I've put my demo project on GitHub if you want to try for youself, though besides what I've posted above it's a standard empty Web API project.
Also happy to try out any suggestions or post more information. Thanks!