【问题标题】:GetCustomAttributes on ParameterDescriptor in IAsyncActionFilter is missingIAsyncActionFilter 中 ParameterDescriptor 上的 GetCustomAttributes 缺失
【发布时间】:2020-02-06 07:35:28
【问题描述】:

使用 .NET Core 2.1。

我正在尝试访问 IAsyncActionFilter 内的 action 参数的属性。

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

在我的 IAsyncActionFilter 中,我想访问参数 p 上的 MyAttribute,但 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();
    }
}

这感觉不对。看到微软自己的文档中提出了这种类型的解决方案,我总是感到沮丧。这是一个等待发生的运行时错误。有没有更好的办法?

【问题讨论】:

    标签: c# asp.net-core .net-core asp.net-core-webapi asp.net-core-2.1


    【解决方案1】:

    方法 1

    您似乎正在尝试绑定操作参数。如果是这种情况,ModelBinding 优先于过滤器,因此您不必将 ActionDescriptor 强制转换为 ControllerActionDescriptor 来检查参数是否具有指定属性,

    在您的场景中,一种更简单且更安全的方法是让您的 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和一个相关的Provider:

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

    如果context.BindingInfo.BindingSource 等于所需属性,则提供程序返回MyFromTempDataModelBinder 的实例。

    另外不要忘记在您的启动中注册此提供程序:

    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

    如果你坚持Filter,你也可以像我们上面那样让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();
        }
    }
    

    【讨论】:

    • 非常感谢。我很欣赏有多种选择。我不确定这是否是模型绑定的用例,因为我不认为我在技术上绑定了参数,但现在我明白了我为什么要这样做。再次感谢。
    • 是的,你是对的,很抱歉我没有花时间更好地阅读 OPs 问题,我将删除我的评论和这个稍后
    猜你喜欢
    • 2011-05-25
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多