GetCustomAttributes для ParameterDescriptor в IAsyncActionFilter отсутствует - PullRequest
1 голос
/ 09 октября 2019

Использование .NET Core 2.1.

Я пытаюсь получить доступ к атрибутам параметра action внутри IAsyncActionFilter.

public IActionResult DoSomething([MyAttribute] MyParameter p) { ... }

В моем IAsyncActionFilter я хотел бы получить доступ к MyAttribute для параметра p, но GetCustomAttributes не существует.

public class MyActionFilter : IAsyncActionFilter
{
    public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // GetCustomAttributes does not exist here...
        var attributes = context.ActionDescriptor.Parameters[0].GetCustomAttributes<MyAttribute>(); 
        return next();
    }
}

В ASP.NET MVC 5.2 вы можете использовать GetCustomAttributes:

https://docs.microsoft.com/en-us/dotnet/api/system.web.mvc.parameterdescriptor.getcustomattributes?view=aspnet-mvc-5.2#System_Web_Mvc_ParameterDescriptor_GetCustomAttributes_System_Boolean_

Как добиться того же в .NET Core?


ОБНОВЛЕНИЕ 1

Кажется, мы можем привести ActionDescriptor к ControllerActionDescriptor для доступа к базовому MethodInfo, а затем к параметрам и их атрибутам.

public class TempDataActionFilter : IAsyncActionFilter
{
    public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor;
        var parameters = 
            from p in actionDescriptor.MethodInfo.GetParameters()
            where p.GetCustomAttributes(typeof(MyAttribute), true) != null
            select p;

        var controller = context.Controller as Controller;
        foreach (var p in parameters)
        {
            // Do something with the parameters that have an attribute 
        }
        return next();
    }
}

Это неправильно. Я всегда встревожен, когда вижу решения такого типа, предлагаемые в собственной документации Microsoft. Это ошибка времени выполнения, ожидающая возникновения. Есть ли лучший способ?

1 Ответ

1 голос
/ 10 октября 2019

Подход 1

Кажется, что вы пытаетесь связать аргументы действия. В этом случае ModelBinding предпочтительнее фильтра, поэтому вам не нужно приводить ActionDescriptor к ControllerActionDescriptor, чтобы проверить, имеет ли параметр указанный атрибут,

Inв вашем сценарии гораздо проще и безопаснее заставить ваш FromTempDataAttribute реализовать IBindingSourceMetadata, чтобы указать, что вы хотите связать данные из TempData:

internal class FromTempDataAttribute : Attribute, IBindingSourceMetadata
{
    public static readonly BindingSource Instance = new BindingSource(
            "id-FromTempData",
            "TempData Binding Source",
            true,
            true
        );
    public BindingSource BindingSource {get{
        return FromTempDataAttribute.Instance;
    }} 
}

, а затем создать ModelBinder и связанный с нимПровайдер:

public class MyFromTempDataModelBinder : IModelBinder
{
    private readonly IServiceProvider sp;

    public MyFromTempDataModelBinder(IServiceProvider sp)
    {
        this.sp = sp;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var factory = this.sp.GetRequiredService<ITempDataDictionaryFactory>();
        var tempData = factory.GetTempData(bindingContext.HttpContext);
        var name = bindingContext.FieldName;
        var o = tempData.Peek(name);
        if (o == null) {
            bindingContext.ModelState.AddModelError(name, $"cannot get {name} from TempData");
        } else {
            var result = Convert.ChangeType(o,bindingContext.ModelType);
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        return Task.CompletedTask;
    }

}

public class FromTempDataBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) { throw new ArgumentNullException(nameof(context)); }
        var has = context.BindingInfo?.BindingSource == FromTempDataAttribute.Instance;
        if(has){
            return new BinderTypeModelBinder(typeof(MyFromTempDataModelBinder));
        }
        return null;
    }
}

Провайдер возвращает экземпляр MyFromTempDataModelBinder, если context.BindingInfo.BindingSource равен необходимому атрибуту.

Также не забудьте зарегистрировать этого провайдера при запуске:

services.AddMvc(opts => {
    opts.ModelBinderProviders.Insert(0, new FromTempDataBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Наконец, вы можете получить данные автоматически:

public IActionResult Test([FromTempDataAttribute] string a, string b )
{
    return Json(new {A = a, B = b,});
}

Подход 2

Если вы настаиваете на фильтре, вы можете также сделать FromTempDataAttribute орудиеIBindingSourceMetadata интерфейс, как мы делаем выше, и затем вы можете получить эти параметры, как показано ниже:

public class TempDataActionFilter : IAsyncActionFilter
{
    public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var parameteres = context.ActionDescriptor.Parameters.Where(p => p.BindingInfo?.BindingSource == FromTempDataAttribute.Instance);
        foreach(var p in parameteres){
            // ...
        }
        return next();
    }
}
...