将依赖项注入到类库中的AuthorizeAttribute中

7

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

[Protected("permission_name")] 

在上述代码中,PermissionAttribute继承自AuthorizeAttribute并使用本地默认值(使用HttpContext创建的DefaultContext)。
在幕后,该属性使用SecurityService来检查用户、角色和权限(SecurityService本身使用客户端提供的持久化服务,他们可以将其连接到应用程序的组合根)。
因此,我的属性需要引用SecurityService才能正常工作。由于属性构造函数只能具有编译时常量,因此我无法使用构造函数注入。
我不想强制我的客户使用DI框架-如果他们选择这样做,他们应该能够在组合根中发现和连接必要的依赖项。
以下是我的选择:
1.让库使用单例SecurityService。
2.使用属性注入,这将起作用,但它会使依赖关系看起来是可选的,而实际上它不是,并且我不知道在MVC应用程序上在授权属性上可以进行属性注入的位置。
可能的解决方法是,在应用程序启动时将SecurityService的实例设置为属性的静态属性,并使用警卫条款防止其被多次设置,就像这样:
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 ;
        }
    }
}

安全服务可以是一个抽象的服务门面,这样它就可以通过不同的实现进行扩展/替换。
有没有更好的方法来解决这个问题?
更新:添加一些代码以展示我将如何执行:
在属性上添加一个公共属性,该属性返回权限名称:
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) ;
  }
}

哦,这太美妙了 流泪



@Darin:是的,要求最少 MVC 3 是可以接受的。已更新标签。 - Shashi Penumarthy
1
相关链接:https://dev59.com/KWw05IYBdhLWcg3wgyLI#7194467 - Mark Seemann
2个回答

7
使用ASP.NET MVC 3,由于新的IFilterProvider接口,您可以在操作过滤器中使用构造函数注入。这样一来,您无需再对控制器操作进行装饰处理以应用操作过滤器,而是通过该接口和使用标记属性即可应用它们。
如果您不想手动实现它,您可以始终使用现有的DI框架(例如Ninject),其提供了一个流畅的方式来定义操作过滤器依赖项。

+1 对于 IFilterProvider 的赞赏,它提供了一个相当干净的解决方案。类似的问题请参考 ASP.NET MVC IFilterProvider and separation of concerns (Castle Windsor)。 - Michał Powaga

0

我的应用程序继承自一个基础的Application类,该类公开了IOC容器。

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。


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