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

时间:2019-07-04 08:19:49

标签: c# asp.net-core asp.net-core-mvc

我所拥有的

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

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

我有一个自定义模型活页夹和活页夹提供程序:

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

将绑定提供程序添加到服务中:

services.AddMvc(options =>
{                    
    options.ModelBinderProviders.Insert(0, new UserTokenBinderProvider());
});

问题

服务器正在加载时,出现以下异常(InvalidOperationException):

  

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

MVC似乎无视我为UserToken类型拥有的自定义绑定程序,并尝试使用默认方法对其进行绑定。 有什么想法吗?

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

1 个答案:

答案 0 :(得分:2)

[ApiController]属性的存在为动作参数引入了Binding source parameter inference。在启动时,操作模型约定将针对所有检测到的控制器操作运行,并推断出绑定源。对于复杂类型,例如您的Guid[]UserToken参数,此推论选择请求正文作为源-就像您将[FromBody]都添加到了 >自己输入这些参数,例如:

public Task<ActionResult> SomeAction(
    [FromBody] Guid[] ids,
    [FromBody] UserToken userToken,
    CancellationToken cancellationToken)

在您的问题中,您声明:

  

MVC似乎无视我为UserToken类型设置的自定义资料夹,并尝试使用默认方法对其进行绑定。

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

如果您要更新UserToken类以使其包含[ModelBinder]属性,则将可以正常工作(甚至可以删除UserTokenBinderProvider):

[ModelBinder(typeof(UserTokenBinderProvider))]
public class UserToken { }

此方法的最大缺点是您的UserToken类将依赖于MVC属性,而这可能并不是您想要的。那么,有没有更好的东西?

现在,您可能想知道为什么我没有为上面的[FromBody]参数显示CancellationToken。这是否意味着CancellationToken得到特殊待遇? Yes, it does。将BindingSourceMetadataProvider添加到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));
});