属性中的依赖注入

Dependency Injection in attributes

我正在尝试将依赖项注入自定义 AuthorizeAttribute,如下所示:

public class UserCanAccessArea : AuthorizeAttribute
{
    readonly IPermissionService permissionService;

    public UserCanAccessArea() :
        this(DependencyResolver.Current.GetService<IPermissionService>()) { }

    public UserCanAccessArea(IPermissionService permissionService)
    {
        this.permissionService = permissionService;
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        string AreaID =
            httpContext.Request.RequestContext.RouteData.Values["AreaID"] as string;

        bool isAuthorized = false;

        if (base.AuthorizeCore(httpContext))
            isAuthorized = permissionService.UserCanAccessArea(AreaID, httpContext.User);

        return isAuthorized;
    }
}

这有效,但似乎作为一个单例解决,这意味着我遇到了 pervious question

中描述的问题

我想做的是使用 属性 注入,但由于我的属性本身没有被 Unity 解析,我无法找到一种方法来配置容器以拦截和解析 属性.我尝试了以下方法:

public class UserCanAccessArea : AuthorizeAttribute
{
    public IPermissionService permissionService { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        string AreaID =
            httpContext.Request.RequestContext.RouteData.Values["AreaID"] as string;

        bool isAuthorized = false;

        if (base.AuthorizeCore(httpContext))
            isAuthorized = permissionService.UserCanAccessArea(AreaID, httpContext.User);

        return isAuthorized;
    }
}

容器:

container.RegisterType<UserCanAccessArea>(new InjectionProperty("permissionService"));

但是 属性 在运行时始终为 null。

有没有人做到这一点,如果有,您有例子吗?

您应该完全避免对属性进行依赖注入。其原因在这篇文章中解释:Dependency Injection in Attributes: don’t do it!。总之,文章解释说:

  • 构造函数注入是不可能的,因为属性实例的创建不能被拦截; CLR 在控制中。
  • 属性 注入的使用很脆弱,因为它会导致 Temporal Coupling,应该避免这种情况。
  • 对属性的依赖注入使得无法验证 容器配置的正确性。
  • MVC 和 Web 等框架 API 缓存属性,非常容易意外创建 captive dependencies 导致错误。

这里有两个选择:

  1. 通过将数据(属性)与其行为(服务)分开,使属性成为被动属性,如 Mark Seemann 的 referenced article and this related article 中所述。
  2. 把你的属性变成humble objects as explained in 。这意味着你:
    1. 将属性中的所有逻辑提取到包含所有依赖项的自定义服务中。
    2. 在您的容器中注册该服务。
    3. 让属性的方法(AuthorizeCore 在你的例子中)只做从服务定位器/DependencyResolver 解析服务并调用服务的方法。这里需要注意的重要一点是,您不能进行构造函数注入,属性 注入,并且服务不能存储在属性私有状态中(正如您已经注意到的那样)。

使用哪个选项:

  • 如果您非常希望保持设计简洁,或者您有多个属性需要以这种方式应用,或者您想要应用的属性在没有定义的程序集中定义,请使用选项 1取决于 System.Web.Mvc.
  • 否则使用选项 2。

ASP.NET Core 中,现在可以通过创建自定义属性、实施 IFilterFactory, or by using TypeFilterAttribute, as well as ServiceFilterAttribute.

来实现

两者都实现 IFilterFactory 并执行您通常在实现 IFilterFactory 的自定义属性中执行的操作,唯一的区别是它们支持排序(如果您愿意,可以在自定义中添加属性)。

但更具体地说 - ServiceFilterAttribute 从实际服务集合中获取您的过滤器实例,这允许您为其定义特定的生命周期,而 TypeFilterAttribute 不使用服务集合来创建你的对象,它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory which is the result of CreateFactory 方法。 (基本上它会创建带有大量表达式树的对象。)TypeFilterAttribute 还允许您为非服务构造函数参数传递参数。两者都使用任何 DI 的服务集合。

对于您现有的代码库,您可以非常简单地执行以下任何操作来在属性的构造函数中实现依赖注入:

  • [TypeFilter(typeof(MyExistingFilterWithConstructorDI))]
  • [TypeFilter(typeof(MyExistingFilterWithConstructorDIAndParams), Arguments = new object[] { "first non-service param", "second non-service param" })]
  • [ServiceFilter(typeof(MyExistingFilterWithConstructorDI)) (您需要将过滤器注册到具有适当生命周期的服务集合中)

现在,就性能而言,如果您最终使用 TypeFilterAttribute,过滤器的类型将按上述方式创建,并带有表达式树,而如果您只是创建自己的 IFilterFactory,你控制那部分,即你简单地实例化你的对象,并且对于任何依赖注入需求 - 你使用提供的 IServiceProvider 作为接口 CreateInstance 方法的一部分。

IsReusable 属性,作为 IFilterFactory 界面的一部分,如果您 更喜欢 框架,可以向您展示您的对象在请求范围之外。这绝不能保证您的过滤器永远只能使用一个对象。

我是这样实现的:

public class ClaimsHandlerAttribute : AuthorizeAttribute, IAuthorizationFilter
{  
    public void OnAuthorization(AuthorizationFilterContext context)
    { 
        var jwtAuthManager =
            context.HttpContext.RequestServices.GetService(typeof(IJwtAuthManager))
                as JwtAuthManager; 

        return;
    }
}