简体   繁体   English

asp.net 核心自定义属性从过滤器中获取 object

[英]asp.net core custom attribute to get object from filter

I am having some ActionFilter s in my asp.net core mvc application, which validate some input data.我的 asp.net 核心 mvc 应用程序中有一些ActionFilter ,它们验证了一些输入数据。 For example, the client sends a userId inside the header, the filter loads that user from a repository, and validates, if the user exists, is active, has a license, and so on.例如,客户端在 header 中发送一个 userId,过滤器从存储库加载该用户,并验证用户是否存在、是否处于活动状态、是否具有许可证等。 This filter is attached to a Controller method.此过滤器附加到 Controller 方法。 The Controller method also needs to collect the same user object. Controller方法还需要采集同一个用户object。 Because of performance, I want to pass that user object, collected inside the filter, to the controller, so the controller does not need to load the same user object again. Because of performance, I want to pass that user object, collected inside the filter, to the controller, so the controller does not need to load the same user object again. I know there are ways to do so, like mentioned here .我知道有办法做到这一点,就像这里提到的那样。

Because of clean code, I wonder if this would be possible, coding an attribute which defines what to retrieve, like the [FromBody] attribute does, for instance.由于代码干净,我想知道这是否可能,编码一个定义检索内容的属性,例如[FromBody]属性。

I could imagine this attribute named [FromFilter("User")] , which takes a parameter to specify the key inside the HttpContext.Items我可以想象这个名为[FromFilter("User")]的属性,它接受一个参数来指定 HttpContext.Items 中的键

A basic implementation could be something like this:一个基本的实现可能是这样的:

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

Neither do I know if this would a be a good idea, nor how to implement such a feature.我也不知道这是否是个好主意,也不知道如何实现这样的功能。 Hopefully someone can me point into the right direction希望有人能指出正确的方向

As far as I know, we couldn't directly pass the object from filter to action.据我所知,我们不能直接将 object 从过滤器传递到操作。

In my opinion, the best solution is creating a custom model binding and then find the user from the repository and pass the user to the action.在我看来,最好的解决方案是创建自定义 model 绑定,然后从存储库中找到用户并将用户传递给操作。

Since the model binding is triggered before the filter, you could get the custom model binding result from the ActionExecutingContext .由于在过滤器之前触发了 model 绑定,因此您可以从ActionExecutingContext获取自定义 model 绑定结果。

Order of execution:执行顺序:

UserModelBinder --> OnActionExecuting --> Index action UserModelBinder --> OnActionExecuting --> 索引动作

More details about to do it, you could refer to below codes:更多细节,你可以参考下面的代码:

Custom model binding:自定义 model 绑定:

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;

    }
}

Controller action and OnActionExecuting method: Controller 动作和 OnActionExecuting 方法:

OnActionExecuting: OnAction执行:

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

Action method:动作方法:

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

    }

Result:结果:

Filter onexecuting:过滤执行:

在此处输入图像描述

Action parameter:动作参数:

在此处输入图像描述

You can use HttpContext.Items for this and create HttpContextItemsModelBinder which will bind model from HttpContext.Items您可以为此使用HttpContext.Items并创建HttpContextItemsModelBinder它将从HttpContext.Items绑定 model

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

Create and register model binder provider创建并注册 model binder provider

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

In Startup.csStartup.cs

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

Create an attribute which will set correct BindingSource to use HttpContextItemsModelBinder创建一个属性,该属性将设置正确的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() { }
}

Usage:用法:

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

Important note重要的提示

Pay attention that I save user model to HttpContext.Items during OnAuthorization and not OnActionExecuting because model binding happens before any action filters run, so HttpContext.Items won't contain user and model binding will fail.请注意,我在OnAuthorization期间将用户 model 保存到HttpContext.Items而不是OnActionExecuting因为 model 绑定发生在任何操作过滤器运行之前,因此HttpContext.Items不会包含用户并且 Z20F35E630DAF49D8CZ 绑定将失败。 You might need to adjust filter code to your needs and to make the solution work as expected.您可能需要根据需要调整过滤器代码并使解决方案按预期工作。

Usage without specifying item name.不指定项目名称的用法。 Parameter name in action method should match key ( "userModel" ) used to store value in HttpContext.Items :操作方法中的参数名称应与用于在HttpContext.Items中存储值的"userModel" )匹配:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM