具有多个参数的 webapi 方法的自定义模型绑定器

Ary*_*dlé 4 c# asp.net-core-mvc asp.net-core

我拥有的

我有一个具有以下方法的 api 控制器(ASP.NET Core MVC):

[HttpPost]
[Route("delete")]
public Task<ActionResult> SomeAction(Guid[] ids,  UserToken userToken, CancellationToken cancellationToken)
{
   ....
}
Run Code Online (Sandbox Code Playgroud)

我有一个自定义模型绑定器和绑定器提供程序:

public class UserTokenBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(UserToken))
        {
            return new BinderTypeModelBinder(typeof(UserTokenBinder));
        }

        return null;
    }
}

public class UserTokenBinder: IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var token = await bindingContext.ActionContext.HttpContext.User.ToUserTokenAsync(CancellationToken.None);

        bindingContext.Result = ModelBindingResult.Success(token ?? UserToken.UnidentifiedUser);
    }
}
Run Code Online (Sandbox Code Playgroud)

将活页夹提供程序添加到服务:

services.AddMvc(options =>
{                    
    options.ModelBinderProviders.Insert(0, new UserTokenBinderProvider());
});
Run Code Online (Sandbox Code Playgroud)

问题

当服务器正在加载时,我收到以下异常 ( InvalidOperationException):

... 'SomeAction' 具有多个指定或推断为从请求正文绑定的参数。每个动作只能从主体绑定一个参数。检查以下参数,并使用“FromQueryAttribute”指定查询绑定,“FromRouteAttribute”指定路由绑定,“FromBodyAttribute”指定要从正文绑定的参数: Guid[] ids, UserToken userToken

MVC 似乎忽略了我对该UserToken类型的自定义绑定器,并尝试使用默认方法绑定它。任何想法为什么?

编辑 在这里收到答案后,打开了一个问题来修改 ASP.NET Core 文档。

Kir*_*kin 9

[ApiController]属性的存在为动作参数引入了绑定源参数推断。在启动时,动作模型约定针对所有检测到的控制器动作运行并推断绑定源。对于复杂类型,例如您的Guid[]UserToken参数,此推理选择请求正文作为源 - 就好像您自己添加[FromBody]这两个参数一样,如下所示:

public Task<ActionResult> SomeAction(
    [FromBody] Guid[] ids,
    [FromBody] UserToken userToken,
    CancellationToken cancellationToken)
Run Code Online (Sandbox Code Playgroud)

在你的问题中,你说:

MVC 似乎忽略了我为 UserToken 类型设置的自定义绑定器,并尝试使用默认方法绑定它。

这不是这里发生的事情。它还没有尝试绑定任何东西——它只是尝试在启动时配置绑定源,甚至在模型绑定发生之前。您已经正确地指示 MVC 使用您的自定义模型绑定器,但是我上面提到的操作模型约定对IModelBinderProvider您添加的内容一无所知。即使这样做了,模型绑定器提供者和类型 ( UserToken)之间的实际关联在GetBinder方法运行之前也是未知的,这仅在需要模型绑定时发生;在配置应用程序模型时不在启动时。

如果你要更新你的UserToken类以包含一个[ModelBinder]属性,它会一切正常(你甚至可以删除UserTokenBinderProvider):

[ModelBinder(typeof(UserTokenBinderProvider))]
public class UserToken { }
Run Code Online (Sandbox Code Playgroud)

这种方法的最大缺点是您的UserToken类将依赖于 MVC 属性,这可能不是您想要的。那么,还有更好的吗?

现在,您可能想知道为什么我没有显示上面[FromBody]CancellationToken参数。这是否意味着CancellationToken得到特殊待遇?是的,确实如此。将 ABindingSourceMetadataProvider添加到将MvcOptions其绑定源指定为 的实例BindingSource.Special。当动作模型约定运行并尝试推断绑定源时,它会看到绑定源已经设置并不管它

要解决您的问题,请BindingSourceMetadataProvider为您的UserToken类型添加一个并使用BindingSource.Special,如下所示:

services.AddMvc(options =>
{                    
    options.ModelBinderProviders.Insert(0, new UserTokenBinderProvider());
    options.ModelMetadataDetailsProviders.Add(
        new BindingSourceMetadataProvider(typeof(UserToken), BindingSource.Special));
});
Run Code Online (Sandbox Code Playgroud)