The web API action parameter is interrupted periodically.

Related question: ApiController PUT and POST API periodically receive null parameters

Background

While testing an existing web API project, I noticed a lot of null support exceptions as a result of the parameter being empty when it was submitted to the action.

The reason, apparently, is a custom message handler registered for log requests while working in development environments. Removing this handler fixes the problem.

I understand that in the Web API I can only read the request body once, and this reading will always cause my parameter to be null, since there will be no binding to the model. For this reason, I use the ReadAsStringAsync () method with ContinueWith to read the body. This seems to behave strangely in ~ 0.2% of requests (during local debugging using Apache Bench).

the 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 application launch

 GlobalConfiguration.Configuration.MessageHandlers.Add(new TestMessageHandler()); 

I am using WebAPI 4.0.30506.0, the latest at the time of publication. All other MS packages in the project also work with the latest version (the demo project linked below has now been updated to reflect this).

Testing

Initial testing was performed using Loadster , which works with load-balanced configuration of IIS 7.5 on Server 2008 R2 with .NET 4.0. 30319. I am replicating this locally in 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 such testing, I see about 1 failure for 500 requests, so ~ 0.2%.

Next steps...

I put my demo project on GitHub if you want to try it myself, although besides the fact that I posted above it is a standard empty web API project.

We are also happy to try out any suggestions or publish additional information. Thanks!

+4
c # asp.net-mvc asp.net-web-api model-binding
source share
1 answer

I am still studying the main reason for this, but so far my gut feeling is that ContinueWith () is executed in a different context or at the point at which the request stream has been deleted or something like that (once I understand that I will definitely update this paragraph).

In terms of fixes, I quickly checked three ways that can handle 500 requests without errors.

The simplest is just to use task.Result , however it has some problems (it can explicitly cause locks , although YMMV).

 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var result = request.Content.ReadAsStringAsync().Result; return base.SendAsync(request, cancellationToken); } 

Then you can make sure that you chain your continuations correctly to avoid any ambiguity regarding the context, however it is pretty ugly (and I'm not 100% sure if it is free from side effects):

 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var result = request.Content.ReadAsStringAsync().ContinueWith(task => { /* do stuff with task.Result */ }); return result.ContinueWith(t => base.SendAsync(request, cancellationToken)).Unwrap(); } 

Finally, the optimal solution seems to use async / await to sweep any thread cuts , obviously this could be a problem if you're stuck in .NET. 4.0.

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var content = await request.Content.ReadAsStringAsync(); Debug.WriteLine(content); return await base.SendAsync(request, cancellationToken); } 
+2
source share

All Articles