Вызов связывателя модели по умолчанию для параметров запроса из AuthorizationHandler с использованием HttpContext - PullRequest
0 голосов
/ 13 декабря 2018

Проблема

В ASP.NET Core 2.2 я внедряю AuthorizationHandler (где у меня есть доступ к HttpContext входящего запроса).

Как вызвать ту же привязку моделидля параметров запроса, который используется MVC из этого AuthorizationHandler?

В идеале я хотел бы написать метод расширения, который позволяет мне написать что-то вроде:

HttpContext.Request.Query.BindValue<ICollection<MembershipType>>("membershipType");

, который возвращает новый экземпляриз ICollection<MembershipType> на основе параметров запроса с именем "membersType" (и где MembershipType - это перечисление).

Фон

Предположим, у меня есть действие контроллера, где сложная модельвыполняется привязка для параметров запроса, например, что-то вроде этого (MembershipType - это перечисление):

[HttpGet]
[Authorize("MyPolicyThatUsesMyRequirement")]
public Task<ActionResult<List<Member>>> GetMembers([FromQuery] ICollection<MembershipType> membershipTypeFilter = null)
{
    // skipping actual implementation
}

Теперь я хочу создать AuthorizationHandler, который будет успешным только для определенных комбинаций записей membershipTypeFilter и пользователяроли.Это означает, что в моем AuthorizationHandler мне нужно получить доступ к параметру membersTypeFilter.

Давайте рассмотрим следующий скелет 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 дает мне доступ к запросупараметры, но только в виде словаря строковых массивов.Конечно, я мог бы разобрать эту коллекцию строк и каким-то образом превратить ее в коллекцию значений enum.Но в этом сценарии крайне важно, чтобы это преобразование выполнялось точно так же, как это делается, когда MVC вызывает мое действие контроллера.Поэтому я ищу способ вызвать тот же механизм связывания, что и MVC.

1 Ответ

0 голосов
/ 14 декабря 2018

Для привязки модели MVC это реализуется Bind .Вам нужно будет использовать его в своем собственном коде.

В моем варианте, это слишком перегружен для вашего требования, лучшим вариантом для вас будет получить значение из строки запроса напрямую.

var parameters = mvcContext
                        .ActionDescriptor
                        .Parameters
                        .Select(s => new
                        {
                            Name = s.Name,
                            Value = mvcContext.HttpContext.Request.Query[s.Name]
                        });

Если вы хотите попробовать с привязкой модели MVC в AuthorizationHandler, попробуйте

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

}
...