首页 > 解决方案 > 如何访问 ActionFilter 中的属性?

问题描述

这是我的动作过滤器:

public class LogAttribute : ActionFilterAttribute
{
    public LogAttribute(ILoggerFactory logger)
    {
        Logger = logger.CreateLogger<LogAttribute>();
    }

    public string UserId { get; set; }

    public string Path { get; set; }

    public ILogger Logger { get; }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var controller = context.Controller as Controller;
        UserId =  User.FindFirstValue(ClaimTypes.NameIdentifier);
        Path = $"{context.HttpContext.Request.Path.Value}[{context.HttpContext.Request.Method}]";
        Logger.LogDebug($"{Path} started. User(id): {UserId}.");
    }
}

我想在我的控制器的其他地方使用 UserId 和 Path 属性。有简洁的方法吗?

标签: asp.net-mvcasp.net-core

解决方案


我同意 Serge 的观点,最好使用 BaseController 来完成这项任务。没有办法用属性清楚地做到这一点。

例如:

public abstract class BaseController<T> : Controller where T : ControllerBase
{
    protected readonly ILogger<T> Logger;

    protected BaseController(ILogger<T> logger)
    {
        Logger = logger;
    }
    protected string UserId { get; set; }
    protected string Path { get; set; }

    [NonAction]
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        UserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        Path = $"{context.HttpContext.Request.Path.Value}[{context.HttpContext.Request.Method}]";
        Logger.LogDebug($"{Path} started. User(id): {UserId}.");
    }
}

public class HomeController : BaseController<HomeController>
{
    public HomeController(ILogger<HomeController> logger): base(logger)
    {
    }
}

或者只是在您的特定控制器中使用覆盖 OnActionExecuting

    public class HomeController: Controller
    {
        private readonly ILogger<HomeController> _logger;
        private string _userId;
        private string _path;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        [NonAction]
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            _userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
            _path = $"{context.HttpContext.Request.Path.Value} 
            [{context.HttpContext.Request.Method}]";
            _logger.LogDebug($"{Path} started. User(id): {UserId}.");
        }
    }

我将向您展示为此使用属性的一些缺点。

1.您可以使用HttpContext.Items实现IDictionary<object,object>并且可用于请求(生命周期作为范围)。但为此,您必须使用资源过滤器。

[AttributeUsage(AttributeTargets.Class)]
public class SampleLogAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            context.HttpContext.Items["path"] = $"{context.HttpContext.Request.Path}[{context.HttpContext.Request.Method}]";
            context.HttpContext.Items["userId"] = "SomeUserIdFromResource";
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }

然后你可以使用 OnActionExecuting 从你的控制器获取它

    private string _path;
    private string _userId;
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        _path = HttpContext.Items["path"] as string;
        _userId = HttpContext.Items["userId"] as string;
    }

不这样做的主要原因 - 您必须知道您的过滤器将值设置为Items并始终在控制器中创建它们。

2.您可以在属性中使用反射设置私有值

public override void OnActionExecuting(ActionExecutingContext context)
{
    var controller = context.Controller;
    var path = controller.GetType().GetField("_path", BindingFlags.NonPublic | BindingFlags.Instance);
    path?.SetValue(controller, $"{context.HttpContext.Request.Path}[{context.HttpContext.Request.Method}]");
    var userId = controller.GetType().GetField("_userId", BindingFlags.NonPublic | BindingFlags.Instance);
    userId?.SetValue(controller, "SomeUserId");
}

如果您忘记创建基本变量,您必须了解 - 您无法访问它们。


推荐阅读