我遇到了一个有趣的设计问题,我正在写一个类库。我有一个AuthorizeAttribute的自定义实现,我希望客户能够像这样使用:
[Protected("permission_name")]
在上面的代码中,PermissionAttribute继承自AuthorizeAttribute并使用本地默认值(使用HttpContext创建的DefaultContext)。
在幕后,该属性使用SecurityService来检查用户,角色和权限(SecurityService本身使用客户端提供的持久性服务,他们可以在应用程序的组合根中连接)。
所以我的属性需要引用SecurityService来运行。由于Attribute构造函数只能有编译时常量,所以我不能使用构造函数注入。
我不想强迫我的客户使用DI框架 - 他们应该能够发现并连接其组合根目录中的必要依赖项而不使用一个IoC库,如果他们愿意的话。
以下是我的选择:
上面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
答案 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