【问题标题】:asp.net core custom attribute to get object from filterasp.net 核心自定义属性从过滤器获取对象
【发布时间】:2020-06-23 17:47:50
【问题描述】:

我的 asp.net 核心 mvc 应用程序中有一些 ActionFilter,用于验证一些输入数据。例如,客户端在标头中发送 userId,过滤器从存储库中加载该用户,并验证用户是否存在、是否处于活动状态、是否具有许可证等。 此过滤器附加到 Controller 方法。 Controller 方法也需要收集相同的用户对象。由于性能原因,我想将过滤器内收集的用户对象传递给控制器​​,因此控制器不需要再次加载相同的用户对象。我知道有办法做到这一点,就像提到的here

由于代码简洁,我想知道这是否可能,编码一个定义检索内容的属性,例如 [FromBody] 属性。

我可以想象这个名为[FromFilter("User")]的属性,它接受一个参数来指定HttpContext.Items里面的键

一个基本的实现可能是这样的:

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromFilterAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
    /// <inheritdoc />
    public BindingSource BindingSource => BindingSource.Custom;
        
    /// <inheritdoc />
    public string Name { get; set; }
}

我不知道这是否是个好主意,也不知道如何实现这样的功能。希望有人能指出正确的方向

【问题讨论】:

标签: c# asp.net-core httpcontext action-filter


【解决方案1】:

据我所知,我们无法直接将对象从过滤器传递到操作。

在我看来,最好的解决方案是创建自定义模型绑定,然后从存储库中找到用户并将用户传递给操作。

由于模型绑定在过滤器之前触发,您可以从ActionExecutingContext获取自定义模型绑定结果。

执行顺序:

UserModelBinder --> OnActionExecuting --> 索引操作

更多细节可以参考以下代码:

自定义模型绑定:

public class UserModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var model = new UserModel()
        {
            id = 1,
            name = "test"
        };

         bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;

    }
}

控制器动作和 OnActionExecuting 方法:

OnActionExecuting:

    public override void OnActionExecuting(ActionExecutingContext context)
    {   
        //ActionArguments["user"] is the parameter name of the action parameter
        var user = context.ActionArguments["user"] as UserModel;


        // Do something before the action executes.
        base.OnActionExecuting(context);
    }

动作方法:

    public async Task<IActionResult> Index([ModelBinder(BinderType = typeof(UserModelBinder))] UserModel user)
    {
       int i =0;
       return View();

    }

结果:

过滤执行:

动作参数:

【讨论】:

    【解决方案2】:

    您可以为此使用HttpContext.Items 并创建HttpContextItemsModelBinder,它将从HttpContext.Items 绑定模型

    public class HttpContextItemsModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var items = bindingContext.HttpContext.Items;
            string name = bindingContext.BinderModelName ?? bindingContext.FieldName;
    
            bindingContext.Result = items.TryGetValue(name, out object item)
                ? ModelBindingResult.Success(item)
                : ModelBindingResult.Failed();
    
            return Task.CompletedTask;
        }
    }
    

    创建和注册模型绑定器提供者

    public static class CustomBindingSources
    {
        public static BindingSource HttpContextItems { get; } = new BindingSource("HttpContextItems", "HttpContext Items", true, true);
    }
    
    public class HttpContextItemsModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.BindingInfo.BindingSource == CustomBindingSources.HttpContextItems)
            {
                return new HttpContextItemsModelBinder();
            }
    
            return null;
        }
    }
    

    Startup.cs

    services
        .AddMvc(options =>
        {
            options.ModelBinderProviders.Insert(0, new HttpContextItemsModelBinderProvider());
            //...
        })
    

    创建一个属性,该属性将设置正确的BindingSource 以使用HttpContextItemsModelBinder

    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class FromHttpContextItemsAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
    {
        public string Name { get; set; }
    
        public BindingSource BindingSource => CustomBindingSources.HttpContextItems;
    
        public FromHttpContextItemsAttribute(string name)
        {
            Name = name;
        }
    
        public FromHttpContextItemsAttribute() { }
    }
    

    用法:

    //in controller
    [HttpGet]
    [ValidateUserFilter]
    public IActionResult TestHttpContextItems([FromHttpContextItems("UserItem")]UserItemModel model)
    {
        return Ok(model);
    }
    
    //your action filter
    public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
           //...
        }
    
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var model = new UserItemModel
            {
                Id = 45,
                Name = "Some user name"
            };
            context.HttpContext.Items["UserItem"] = model;
        }
    }
    

    重要提示

    注意我在OnAuthorization 期间将用户模型保存到HttpContext.Items 而不是OnActionExecuting,因为模型绑定发生在任何操作过滤器运行之前,所以HttpContext.Items 不会包含用户并且模型绑定将失败。您可能需要根据需要调整过滤器代码并让解决方案按预期工作。

    在不指定项目名称的情况下使用。动作方法中的参数名称应匹配 key ("userModel") 用于在 HttpContext.Items 中存储值:

    //in controller
    [HttpGet]
    [ValidateUserFilter]
    public IActionResult TestHttpContextItems([FromHttpContextItems]UserItemModel userModel)
    {
        return Ok(userModel);
    }
    
    //action filter
    public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
           //...
        }
    
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            //...
            context.HttpContext.Items["userModel"] = model;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-08
      • 2019-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      • 1970-01-01
      • 2014-10-20
      相关资源
      最近更新 更多