属性注入依赖

38

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

这个方法可以正常工作,但似乎被解析为单例模式,这意味着我会遇到我之前问题中描述的问题。

我想做的是使用属性注入,但由于我的Attribute本身没有被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"));

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

是否有人已经实现了这一点,如果是的话,你有一个例子吗?

3个回答

73
你应该完全避免将依赖项注入到属性中。这篇文章中解释了原因:不要在属性中进行依赖项注入!。简而言之,文章解释了以下几点:
  • 构造函数注入不可能,因为无法拦截属性实例的创建;CLR 控制着这一过程。
  • 使用属性注入是脆弱的,因为它会导致时间耦合,应该避免这种情况。
  • 在属性中进行依赖项注入会使验证容器配置的正确性变得不可能。
  • MVC 和 Web API 等框架会缓存属性,很容易意外地创建固有依赖,从而导致错误。

在这里你有两个选择:

  1. 将属性分离为数据(属性)和行为(服务),使其成为被动的,详见参考文章和 Mark Seemann 的相关文章
  2. 将属性转换为谦逊对象,详见这个答案。这意味着你需要:
    1. 从属性中提取所有逻辑到一个包含所有依赖项的自定义服务中。
    2. 在容器中注册该服务。
    3. 让属性的方法(在你的例子中是 AuthorizeCore)仅从服务定位器 / DependencyResolver 中解析服务并调用服务的方法。重要的是要注意,你不能进行构造函数注入、属性注入,也不能将服务存储在属性的私有状态中(正如你已经注意到的那样)。

应该使用哪个选项:

  • 如果你非常注重保持设计的清晰性,或者你需要以这种方式应用多个属性,或者你想将属性应用于不依赖于 System.Web.Mvc 的程序集中,请使用选项 1。
  • 否则,请使用选项 2。

我一直在探索Option1,似乎如果您已经指定了dbcontext要在InRequestScope(Ninject)中解析,则无法使用它。否则完美运作。我首先尝试了服务定位器(反模式,但解决了服务对象的创建)。自定义授权属性的问题在于它是在运行时创建的,并且不遵循InRequestScope。如果我错了,请纠正我。 - Lyubomir Velchev
我想使用InRequestScope选项,因为希望在使用不同的存储库时拥有相同的dbcontext,并能够使用UnitOfWork - 一个地方来调用dbcontext中的savechanges。 - Lyubomir Velchev
一个想法是不使用DI来处理服务,而是在授权过滤器中创建一个实际的对象。这是一个好方法吗?这将使我的代码变得丑陋,因为我想要的服务还有其他依赖的服务和存储库... - Lyubomir Velchev
1
@LyubomirVelchev:请仔细再次阅读选项1中提到的文章。在那种情况下,您的DbContext的生命周期是无关紧要的。属性可以是单例,没有任何问题,因为您不会将任何内容注入属性。属性只是数据。但是(像往常一样),您必须确保您的动作过滤器服务的生命周期短于或等于其依赖项,因为您将会得到captive dependencies - Steven
2
在AuthorizeCore中解决依赖关系非常重要,不要将依赖项的实例与属性一起存储。 - Keith Sirmons
1
@Steven - 你可以通过使用IFilterProvider从DI容器中按需解析过滤器来避免固定依赖,而不是在静态的GlobalFilterCollection中注册过滤器。 - NightOwl888

10
在ASP.NET Core中,现在可以通过创建自定义属性、实现IFilterFactory或使用TypeFilterAttribute以及ServiceFilterAttribute来实现。两者都实现了IFilterFactory,并且执行您通常在自定义属性中执行的操作,唯一的区别是它们支持排序(如果您希望在自定义属性中添加排序,则可以添加)。请保留HTML标签。
更具体地说,ServiceFilterAttribute 从实际的服务集合中获取您的过滤器实例,这允许您为其定义特定的生命周期,而 TypeFilterAttribute 不使用服务集合来创建对象,它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory,它是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,则可以控制该部分,即您只需实例化对象,并对于任何依赖注入需求-在接口的CreateInstance方法中使用提供的IServiceProvider

IsReusable属性作为IFilterFactory接口的一部分存在,用于显示您偏好框架在请求范围之外使用您的对象。这并不保证您将始终使用单个对象进行过滤。


据我所知,这是.NET Core的最佳选择。 - Michael Silver
很遗憾,AuthorizeAttribute 没有实现 IFilterMetadata 接口。 - romanoza

0
我是这样实现的:
public class ClaimsHandlerAttribute : AuthorizeAttribute, IAuthorizationFilter
{  
    public void OnAuthorization(AuthorizationFilterContext context)
    { 
        var jwtAuthManager =
            context.HttpContext.RequestServices.GetService(typeof(IJwtAuthManager))
                as JwtAuthManager; 

        return;
    }
}

2
这基本上是Steven所接受的答案中的第二个选项。 - Brandon Rader
3
为什么你对我们大声喊叫?无用的“警报”是怎么回事?请阅读[答案]。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接