将依赖项注入类库中的AuthorizeAttribute

时间:2012-01-06 15:39:27

标签: c# .net asp.net-mvc-3 dependency-injection

我遇到了一个有趣的设计问题,我正在写一个类库。我有一个AuthorizeAttribute的自定义实现,我希望客户能够像这样使用:

[Protected("permission_name")] 

在上面的代码中,PermissionAttribute继承自AuthorizeAttribute并使用本地默认值(使用HttpContext创建的DefaultContext)。

在幕后,该属性使用SecurityService来检查用户,角色和权限(SecurityService本身使用客户端提供的持久性服务,他们可以在应用程序的组合根中连接)。

所以我的属性需要引用SecurityService来运行。由于Attribute构造函数只能有编译时常量,所以我不能使用构造函数注入。

我不想强迫我的客户使用DI框架 - 他们应该能够发现连接其组合根目录中的必要依赖项而不使用一个IoC库,如果他们愿意的话。

以下是我的选择:

  • 让图书馆使用单一的SecurityService。
  • 使用属性注入,这可以工作但是
    1. 它会使依赖项看起来是可选的,它不是和
    2. 我不知道在一个授权属性的MVC应用程序中我可以在哪里进行属性注入。

上面2.的一个可能的解决方案是在应用程序启动时将SecurityService的实例设置为属性的静态属性,并使用guard子句来阻止它被设置多次,如下所示:

class ProtectedAttribute : ...
{
    private static ISecurityService _SecurityService ;
    public static ISecurityService SecurityService
    {
        get 
        {
            return _SecurityService ;
        }
        set 
        {
            if (_SecurityService != null)
                throw new InvalidOperationException("You can only set the SecurityService once per lifetime of this app.") ;
            _SecurityService = value ;
        }
    }
}

SecurityService可以是一个抽象的服务外观,以便可以通过不同的实现进行扩展/替换。

有没有更好的方法来解决这个问题?

更新:添加一些代码以显示我将如何执行此操作:

在返回权限名称的属性上添加公共属性:

public class ProtectedAttribute : ...
{
  private string _Permission ;
  public string Permission { get { return _Permission ; } /*...*/ }

  public ProtectedAttribute(string permission) { /*...*/ }
}

设置授权过滤器并通过Ninject配置依赖关系(如果使用Ninject):

using Ninject.Web.Mvc.FilterBindingSyntax;

public class MyModule : Ninject.Modules.NinjectModule
{
  public override void Load()
  {
    // mySecurityService instance below can have a singleton lifetime - perfect!
    this.BindFilter<MyAuthorizationFilter>(FilterScope.Action, 0)
        .WhenActionMethodHas<ProtectedAttribute>()
        .WithConstructorArgument("securityService", mySecurityService)
        .WithConstructorArgumentFromActionAttribute<ProtectedAttribute>("permission", p => p.PermissionName) ;
  }
}

哦,它是......美丽的 sniffle

2 个答案:

答案 0 :(得分:7)

使用ASP.NET MVC 3,您可以使用构造函数注入和动作过滤器,这要归功于新的IFilterProvider。这样,您不再需要使用操作过滤器来装饰控制器操作。您可以使用此界面并使用标记属性来应用它们。

如果您不想手动实现它,您可以始终使用现有的DI框架,例如Ninject,它提供fluent way来定义动作过滤器依赖项。

答案 1 :(得分:0)

我的应用程序继承自公开IOC容器的基础Application类。

public interface IInjectableApplication
    {
        IUnityContainer Container { get; }
    }

然后我有一个基本属性类,它知道这个

public abstract IocAwareActionFilterAttribute : ActionFilterAttribute{
    protected T ResolveItem<T>(ResultExecutedContext context)
        {
            var app = context.HttpContext.ApplicationInstance as IInjectableApplication;
            if (app == null) { throw new NullReferenceException("Application is not IInjectable."); }

            T c = (T)app.Container.Resolve(typeof(T));

            if (c == null) { throw new NullReferenceException(string.Format("Could not find injected {0}.", typeof(T).FullName)); }
            return c;
        }

}

虽然这不是真正的注入,因为属性不是“正常”构造的,这提供了类似的行为。没有理由不适应其他IOC