【问题标题】:Web API action parameter is intermittently nullWeb API 操作参数间歇性地为空
【发布时间】:2013-11-05 20:15:13
【问题描述】:

相关问题:Web API ApiController PUT and POST methods receive null parameters intermittently

背景

在对现有 Web API 项目进行负载测试时,我注意到很多空引用异常,因为在发布到操作时参数为空。

原因似乎是在开发环境中运行时注册了一个自定义消息处理程序以记录请求。删除此处理程序可解决问题。

我了解在 Web API 中我只能读取一次请求正文,并且读取它总是会导致我的参数为空,因为无法进行模型绑定。出于这个原因,我使用带有 ContinueWith 的 ReadAsStringAsync() 方法来读取正文。看起来这在大约 0.2% 的请求中表现异常(在使用 Apache Bench 进行本地调试期间)。

代码

在最基本的层面上,我有以下几点:

型号

public class User
{
    public string Name { get; set; }
}

API 控制器

public class UsersController : ApiController
{
    [HttpPost]
    public void Foo(User user)
    {
        if (user == null)
        {
            throw new NullReferenceException();
        }
    }
}

消息处理程序

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);
    }
}

...在应用启动时注册

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

我使用的是发布时最新的 WebAPI 4.0.30506.0。项目中的所有其他 MS 包也在运行最新版本(下面链接的演示项目现已更新以反映这一点)。

测试

初始测试是使用 Loadster 在带有 .NET 4.0.30319 的 Server 2008 R2 上针对负载平衡的 IIS 7.5 设置运行的。我正在使用 Apache Bench 使用 .NET 4.5.50709 在 Windows 7 上的 IIS 7.5 上本地复制它。

ab -n 500 -c 25 -p testdata.post -T "application/json" http://localhost/ModelBindingFail/api/users/foo

testdata.post 包含的位置

{ "Name":"James" }

通过此测试,我发现 500 个请求中大约有 1 个失败,因此约为 0.2%。

接下来的步骤...

如果您想亲自尝试,我已将我的演示项目放在GitHub,尽管除了我在上面发布的内容之外,它是一个标准的空 Web API 项目。

也很乐意尝试任何建议或发布更多信息。谢谢!

【问题讨论】:

  • 你可能已经检查过了——但你确定是控制器方法抛出了 NullReferenceException 吗?另外,您还没有在任何地方发布实际的任务延续方法,也许这会有所帮助
  • @JoannaTurban 是的,这是我在检查参数是否为空时抛出的异常(我的演示控制器 github.com/jamesantrobus/WebAPI-ModelBindingError/blob/master/… 中的第 13 行)。例如,我实际上并没有在该任务中做任何事情,我仍然可以从我的生产应用程序中复制问题。

标签: c# asp.net-mvc asp.net-web-api model-binding


【解决方案1】:

我仍在调查此问题的根本原因,但到目前为止,我的直觉是 ContinueWith() 是在不同的上下文中执行的,或者在请求流已被处理的点或类似的情况下执行(一次我确定我会更新这一段)。

在修复方面,我已经快速测试了三个可以处理 500 个请求而没有错误的三个。

最简单的是只使用task.Result,但这确实有一些问题(它可以apparently cause deadlocks,虽然是YMMV)。

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

接下来,您可以确保正确链接您的延续以避免任何关于上下文的歧义,但是它非常丑陋(而且我不能 100% 确定它是否没有副作用):

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();
}

最后,最佳解决方案似乎是使用 async/await 到 sweep away any threading nasties,如果您卡在 .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);
}

【讨论】:

  • 该死的,忘了这个。我最终在 MSDN 论坛上得到了类似的答案,尽管这得到了更好的解释。使用 async/await 似乎是最好的解决方案,已经投入生产一个月左右,没有任何问题。
  • 我也遇到了与 OP 相同的问题,这个答案非常有帮助。我遇到的问题之一是在登台/测试环境(不是本地开发)上出现问题,并且在您连接提琴手后问题停止!可能与减慢请求有关的事情并没有真正深入人心,但注意到一堆 ContinueWith 可以很容易地被 await/async 替换,这已经解决了问题
猜你喜欢
  • 1970-01-01
  • 2016-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-01
  • 2020-02-04
  • 2015-04-11
  • 1970-01-01
相关资源
最近更新 更多