我所拥有的
我有一个采用以下方法的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文档。
答案 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));
});