【问题标题】:Dependency Injection in attributes属性中的依赖注入
【发布时间】:2015-07-07 01:18:07
【问题描述】:

我正在尝试将依赖项注入自定义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"));

但该属性在运行时始终为空。

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

【问题讨论】:

    标签: c# asp.net-mvc authentication dependency-injection unity-container


    【解决方案1】:

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

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

    你有两个选择:

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

    使用哪个选项:

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

    【讨论】:

    • 我一直在探索选项 1,如果您已指定要解析 InRequestScope (Ninject) 的 dbcontext,则似乎无法使用此选项。否则完美运行。我首先尝试使用服务定位器(反模式,但解决了服务对象的创建)。自定义授权属性的问题在于它是在运行时创建的并且不遵循 InRequestScope。如果我错了,请纠正我。
    • 我想使用 InRequestScope 选项,因为希望在使用不同的存储库时拥有一个相同的 dbcontext,并且能够使用 UnitOfWork - 一个在 dbcontext 中调用 savechanges 的地方。
    • @LyubomirVelchev:请再次仔细阅读选项 1 中提到的the article。在这种情况下,您的 DbContext 的生活方式是无关紧要的。属性可以是单例,没有任何问题,因为您没有向属性中注入任何东西。该属性只是数据。但是(一如既往)您必须确保您的操作过滤器服务的生命周期短于或等于其依赖项,因为您将获得captive dependencies
    • 在AuthorizeCore中解析依赖非常重要,不要用Attribute存储依赖的实例。
    • @Steven - 您可以通过using IFilterProvider to resolve the filter on demand from the DI container 避免强制依赖,而不是在静态GlobalFilterCollection 中注册过滤器。
    【解决方案2】:

    ASP.NET Core 中,现在可以通过创建自定义属性、实现 IFilterFactory 或使用 TypeFilterAttribute 以及 ServiceFilterAttribute 来实现。

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

    但更具体地说 - ServiceFilterAttribute 从实际的服务集合中获取过滤器的实例,这允许您为其定义特定的生命周期,而 TypeFilterAttribute 不使用服务集合来创建您的对象,它使用Microsoft.Extensions.DependencyInjection.ObjectFactoryCreateFactory 方法的结果。 (基本上,它使用大量表达式树创建您的对象。)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 接口的一部分,用于显示您是否更喜欢框架在请求范围之外使用您的对象。这绝不会保证您将永远被过滤器的单个对象卡住。

    【讨论】:

    • AFAICT,这是 .NET core 的最佳选择。
    • 很遗憾,AuthorizeAttribute 没有实现 IFilterMetadata。
    【解决方案3】:

    我是这样实现的:

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

    【讨论】:

    • 这基本上是 Steven 接受的答案中的选项 2。
    • 你为什么对我们大喊大叫?没用的“警觉”绒毛是怎么回事?请阅读How to Answer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-17
    • 2019-02-17
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多