在给定 HttpContext 的情况下,从 AuthorizationHandler 为查询参数调用默认模型联编程序
Invoking default model binder for query parameters from an AuthorizationHandler, given a HttpContext
问题
在 ASP.NET Core 2.2 中,我正在实现一个 AuthorizationHandler(我可以在其中访问传入请求的 HttpContext)。
如何从 AuthorizationHandler 中为 MVC 使用的查询参数调用相同的模型绑定器?
理想情况下,我想编写一个扩展方法,允许我编写如下内容:
HttpContext.Request.Query.BindValue<ICollection<MembershipType>>("membershipType");
returns ICollection<MembershipType>
的新实例基于名为 "membershipType" 的查询参数(其中 MembershipType
是一个枚举)。
背景
假设我有一个控制器操作,其中执行查询参数的复杂模型绑定,例如像这样的东西(MembershipType
是一个枚举):
[HttpGet]
[Authorize("MyPolicyThatUsesMyRequirement")]
public Task<ActionResult<List<Member>>> GetMembers([FromQuery] ICollection<MembershipType> membershipTypeFilter = null)
{
// skipping actual implementation
}
现在我想创建一个 AuthorizationHandler,它只对 membershipTypeFilter
条目和用户角色的某些组合成功。这意味着在我的 AuthorizationHandler 中,我需要访问 membershipTypeFilter 参数。
让我们看一下 AuthorizationHandler 的以下框架:
class MyRequirement : AuthorizationHandler<MyRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var mvcContext = context.Resource as AuthorizationFilterContext;
// TODO: need access to query parameters here
return Task.CompletedTask;
}
}
mvcContext.HttpContext.Request.Query
让我可以访问查询参数,但只能以字符串数组字典的形式。当然,我可以解析该字符串集合并以某种方式将其转换为枚举值集合。但在这种情况下,以与 MVC 调用我的控制器操作时完全相同的方式完成此转换至关重要。所以我正在寻找一种方法来调用与 MVC 使用的相同的模型绑定器。
对于MVC模型绑定,由Bind实现。您需要在自己的代码中使用它。
在我看来,这对您的要求来说太过载了,对您来说更好的选择是直接从查询字符串中获取值。
var parameters = mvcContext
.ActionDescriptor
.Parameters
.Select(s => new
{
Name = s.Name,
Value = mvcContext.HttpContext.Request.Query[s.Name]
});
如果您想在 AuthorizationHandler
中尝试使用 MVC 模型绑定,请尝试
public class MyNewRequirement : AuthorizationHandler<MyRequirement>, IAuthorizationRequirement
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var mvcContext = context.Resource as AuthorizationFilterContext;
//required service
var _mvcOptions = mvcContext.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
var parameterBinder = mvcContext.HttpContext.RequestServices.GetRequiredService<ParameterBinder>();
var _modelBinderFactory = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelBinderFactory>();
var _modelMetadataProvider = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
var controllerContext = new ControllerContext(mvcContext);
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_mvcOptions.ValueProviderFactories.ToArray());
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var parameters = controllerContext.ActionDescriptor.Parameters;
var parameterBindingInfo = GetParameterBindingInfo(
_modelBinderFactory,
_modelMetadataProvider,
controllerContext.ActionDescriptor,
_mvcOptions);
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata;
if (!modelMetadata.IsBindingAllowed)
{
continue;
}
var model = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
parameter,
modelMetadata,
value: null);
}
}
private static BinderItem[] GetParameterBindingInfo(
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
ControllerActionDescriptor actionDescriptor,
MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == 0)
{
return null;
}
var parameterBindingInfo = new BinderItem[parameters.Count];
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.
metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
}
else
{
// For backward compatibility, if there's a custom model metadata provider that
// only implements the older IModelMetadataProvider interface, access the more
// limited metadata information it supplies. In this scenario, validation attributes
// are not supported on parameters.
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
}
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
Metadata = metadata,
CacheToken = parameter,
});
parameterBindingInfo[i] = new BinderItem(binder, metadata);
}
return parameterBindingInfo;
}
private struct BinderItem
{
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
{
ModelBinder = modelBinder;
ModelMetadata = modelMetadata;
}
public IModelBinder ModelBinder { get; }
public ModelMetadata ModelMetadata { get; }
}
}
问题
在 ASP.NET Core 2.2 中,我正在实现一个 AuthorizationHandler(我可以在其中访问传入请求的 HttpContext)。
如何从 AuthorizationHandler 中为 MVC 使用的查询参数调用相同的模型绑定器?
理想情况下,我想编写一个扩展方法,允许我编写如下内容:
HttpContext.Request.Query.BindValue<ICollection<MembershipType>>("membershipType");
returns ICollection<MembershipType>
的新实例基于名为 "membershipType" 的查询参数(其中 MembershipType
是一个枚举)。
背景
假设我有一个控制器操作,其中执行查询参数的复杂模型绑定,例如像这样的东西(MembershipType
是一个枚举):
[HttpGet]
[Authorize("MyPolicyThatUsesMyRequirement")]
public Task<ActionResult<List<Member>>> GetMembers([FromQuery] ICollection<MembershipType> membershipTypeFilter = null)
{
// skipping actual implementation
}
现在我想创建一个 AuthorizationHandler,它只对 membershipTypeFilter
条目和用户角色的某些组合成功。这意味着在我的 AuthorizationHandler 中,我需要访问 membershipTypeFilter 参数。
让我们看一下 AuthorizationHandler 的以下框架:
class MyRequirement : AuthorizationHandler<MyRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var mvcContext = context.Resource as AuthorizationFilterContext;
// TODO: need access to query parameters here
return Task.CompletedTask;
}
}
mvcContext.HttpContext.Request.Query
让我可以访问查询参数,但只能以字符串数组字典的形式。当然,我可以解析该字符串集合并以某种方式将其转换为枚举值集合。但在这种情况下,以与 MVC 调用我的控制器操作时完全相同的方式完成此转换至关重要。所以我正在寻找一种方法来调用与 MVC 使用的相同的模型绑定器。
对于MVC模型绑定,由Bind实现。您需要在自己的代码中使用它。
在我看来,这对您的要求来说太过载了,对您来说更好的选择是直接从查询字符串中获取值。
var parameters = mvcContext
.ActionDescriptor
.Parameters
.Select(s => new
{
Name = s.Name,
Value = mvcContext.HttpContext.Request.Query[s.Name]
});
如果您想在 AuthorizationHandler
中尝试使用 MVC 模型绑定,请尝试
public class MyNewRequirement : AuthorizationHandler<MyRequirement>, IAuthorizationRequirement
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var mvcContext = context.Resource as AuthorizationFilterContext;
//required service
var _mvcOptions = mvcContext.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
var parameterBinder = mvcContext.HttpContext.RequestServices.GetRequiredService<ParameterBinder>();
var _modelBinderFactory = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelBinderFactory>();
var _modelMetadataProvider = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
var controllerContext = new ControllerContext(mvcContext);
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_mvcOptions.ValueProviderFactories.ToArray());
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var parameters = controllerContext.ActionDescriptor.Parameters;
var parameterBindingInfo = GetParameterBindingInfo(
_modelBinderFactory,
_modelMetadataProvider,
controllerContext.ActionDescriptor,
_mvcOptions);
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata;
if (!modelMetadata.IsBindingAllowed)
{
continue;
}
var model = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
parameter,
modelMetadata,
value: null);
}
}
private static BinderItem[] GetParameterBindingInfo(
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
ControllerActionDescriptor actionDescriptor,
MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == 0)
{
return null;
}
var parameterBindingInfo = new BinderItem[parameters.Count];
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.
metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
}
else
{
// For backward compatibility, if there's a custom model metadata provider that
// only implements the older IModelMetadataProvider interface, access the more
// limited metadata information it supplies. In this scenario, validation attributes
// are not supported on parameters.
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
}
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
Metadata = metadata,
CacheToken = parameter,
});
parameterBindingInfo[i] = new BinderItem(binder, metadata);
}
return parameterBindingInfo;
}
private struct BinderItem
{
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
{
ModelBinder = modelBinder;
ModelMetadata = modelMetadata;
}
public IModelBinder ModelBinder { get; }
public ModelMetadata ModelMetadata { get; }
}
}