【问题标题】:How do I disable automatic model binding for a specific ASP.NET Core 5.0 Web API action?如何禁用特定 ASP.NET Core 5.0 Web API 操作的自动模型绑定?
【发布时间】:2021-03-29 22:14:09
【问题描述】:

我有一个第三方专有应用程序,我需要在我的 ASP.NET Core 5.0 Web API 应用程序中为此编写一个 API 端点。

第三方应用程序发出一个 HTTP post 请求,请求正文中只有二进制数据,以及内容类型 application/x-www-form-urlencoded 或有时是 application/octet-stream(有点随机,但数据相同)。

我的动作处理程序如下所示:

[Route("~/Validation")]
[ApiController]
public class ValidationController : ControllerBase
{
    [HttpPost("{requestId}")]
    [Consumes(@"application/octet-stream", @"application/x-www-form-urlencoded")]
    [Produces(@"application/octet-stream")]
    public async Task<IActionResult> Validation_Post([FromRoute] string requestId)
    {
        byte[] rawRequestBody = Array.Empty<byte>();
        {
            long streamInitialPos = 0;
            if (Request.Body.CanSeek) // rewind for this read.
            {
                streamInitialPos = Request.Body.Position;
                Request.Body.Seek(0, SeekOrigin.Begin);
            }
            using (var ms = new MemoryStream())
            {
                await Request.Body.CopyToAsync(ms);
                rawRequestBody = ms.ToArray() ?? throw new NullReferenceException();
            }
            if (Request.Body.CanSeek) // rewind to initial position.
                Request.Body.Seek(streamInitialPos, SeekOrigin.Begin);
        }

        // TODO: Handle rawRequestBody data.

        return new FileContentResult(new byte[] { 1 }, @"application/octet-stream")
        {
            EnableRangeProcessing = true,
            LastModified = DateTime.UtcNow
        };
    }

当第三方应用程序将其 HTTP 发布请求发送到我的 API 端点时,我的 API 应用程序崩溃并显示 System.ArgumentException

Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Error: Connection ID "18374686481282236432", Request ID "80000011-0000-ff00-b63f-84710c7967bb": An unhandled exception was thrown by the application.

System.ArgumentException: The key '[omitted binary data]' is invalid JQuery syntax because it is missing a closing bracket. (Parameter 'key')
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.NormalizeJQueryToMvc(StringBuilder builder, String key)
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.GetValues(IEnumerable`1 originalValues, Int32 valueCount)
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProviderFactory.AddValueProviderAsync(ValueProviderFactoryContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ActionContext actionContext, IList`1 factories)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.TryCreateAsync(ActionContext actionContext, IList`1 factories)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()

Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished HTTP/1.1 POST http://localhost:10891/validation/dummy application/x-www-form-urlencoded 11072 - 500 - - 164.0024ms

日志显示正在使用正确的路由操作。

如何仅为此特定操作处理程序禁用自动模型绑定?

提醒:我无法对第三方应用程序进行任何更改。我必须处理我收到的东西。我知道请求内容类型错误。请不要在这方面做任何笔记。


编辑:我已经找到了这个错误的表面原因。当我从函数签名中删除[FromRoute] string requestId 时,不会发生错误。当我重新引入它时,错误再次出现。

不起作用(导致 ASP.NET Core 内部异常):

public async Task<IActionResult> Validation_Post([FromRoute] string requestId)

有效:

public async Task<IActionResult> Validation_Post()

但是,我需要通过Request.RouteValues["requestId"]访问路由变量。

无论如何,问题仍然存在: 如何仅为此特定操作处理程序禁用自动模型绑定?

【问题讨论】:

  • ArgumentException 是哪一行? stream 来自哪里?
  • @mxmissile:感谢您指出stream,我已经大大减少了代码,以至于我在代码sn-p中引入了错误。动作源代码现已修复,我在问题中添加了完整的堆栈跟踪。
  • @mxmissile:而且没有线。我猜它是 ASP.NET Core 内部的(框架)。
  • @RoarS.: ~ 指的是根 mount 点(与 / 的实际根路径相反)。删除 [ApiController] 没有帮助,请参阅我的问题更新。
  • @mxmissile:ASP.NET Core 模型内部绑定类包括对特定 jQuery 字段命名约定的特殊处理。看一下NormalizeJQueryToMvc()方法,它负责抛出这个异常。这与数据绑定期间字段如何映射到对象有关。此类查找 JQuery 语法并将其规范化以使用 MVC 语法,以便模型绑定的其余部分不需要考虑差异。

标签: c# asp.net-core asp.net-core-webapi model-binding asp.net-core-5.0


【解决方案1】:

我认为您实际上已经找到了解决方案,或者至少确定了所有关键组件。请允许我逐步介绍它们,以便您将它们组合成一个解决方案。

禁用模型绑定

模型绑定仅在您的操作有参数时发生 - 否则没有要绑定的模型。这可能是一个实际的模型,例如 POCO,或者只是一个值类型,例如您在此处拥有的字符串参数;相同的基本过程适用于两者。这就是您在删除参数时没有收到错误消息的原因,也是您已经有效回答问题的原因。

在没有模型绑定的情况下提取路由值

拥有参数的唯一目的或好处是让它参与模型绑定。因此,如果您想禁用模型绑定,则没有理由维护该参数。没有它,您仍然可以使用Request 属性来提取请求值,包括表单字段、查询字符串参数、路由变量、请求标头等。因此,在这种情况下,您仍然可以在您的操作中调用例如 Request.RouteValues["requestId"],正如您所指出的。直接调用RouteValueDictionary 不会触发对JQueryKeyValuePairNormalizer 类的调用,因此您不会遇到同样的异常。

潜在的路径模糊

也就是说,根据您的路由和路由参数的定义方式,您可能遇到问题,例如,如果您依赖此参数来消除操作的其他重载之间的歧义,那么这是值得的意识到。

根本问题

不过,要返回核心错误,我还建议您评估失败请求的请求数据,以确保没有任何可能与包含格式错误的索引器的字段名称混淆。根据您的描述,我不希望出现这种情况,除非它以某种方式试图将二进制数据解析为表单数据预期的键/值对,这可能是由于错误的内容类型。无论如何,the line of code that’s throwing the error 会发生,特别是当有一个包含[ 的字段没有对应的] 时。例如,它应该与以下查询字符串一起出现:

?key[0=5

如果这样的事情是您传入请求的一致固定装置,那很可能是罪魁祸首。显然,由于您无法控制客户端,因此您无能为力,但隔离会很有用,这样您就可以为未来的实施提供指导。

【讨论】:

  • 谢谢。我有一个假设,当函数签名中只有 [FromRoute](上下文/源不是正文)时,模型绑定将是“智能”的,它不会尝试解析正文。
  • @burnersk:我听到了。这不仅看起来应该是一个合乎逻辑的假设,而且人们会期望它(略微)性能更高,因为不需要(进一步)解析不是的内容将需要数据绑定。我的公司有一个我们编写的映射库,它与模型绑定非常相似,正是出于这个原因,它采用了一种更具反应性的方法。
【解决方案2】:

也许这可以禁用模型绑定?

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<FormFileValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    相关资源
    最近更新 更多