我遇到了一个有趣的设计问题,与我正在编写的类库有关。我有一个自定义的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) ;
}
}
哦,这太美妙了 流泪