Web API action parameter is intermittently null

落花浮王杯 提交于 2019-12-22 08:38:11

问题


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!


回答1:


I'm still investigating the root cause of this but so far my gut feeling is that ContinueWith() is being executed in a different context, or at a point by which the request stream has been disposed or something like that (once I figure that out for sure I will update this paragraph).

In terms of fixes, I've quickly road tested three that can handle 500 requests with no errors.

The simplest is to just use task.Result, this does however have some issues (it can apparently cause deadlocks, although YMMV).

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

Next, you can ensure you are properly chaining your continuations to avoid any ambiguity about context, it is however quite ugly (and I'm not 100% sure if it is side effect free):

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 appears to make use of async/await to sweep away any threading nasties, obviously this may be an issue if you are stuck on .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);
}


来源:https://stackoverflow.com/questions/18256817/web-api-action-parameter-is-intermittently-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!