将dependency injection到ASP.NET MVC 3操作filter中。 这种方法有什么问题?
这是设置。 假设我有一些需要服务实例的动作filter:
public interface IMyService { void DoSomething(); } public class MyService : IMyService { public void DoSomething(){} }
然后我有一个ActionFilter需要一个该服务的实例:
public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; // <--- How do we get this injected public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } }
在MVC 1/2注入依赖到行动filter是屁股有点痛苦。 最常见的方法是使用自定义操作调用程序,可以在这里看到: http : //www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/这个解决方法背后的主要动机是因为下面的方法被认为是容易和紧密耦合的容器:
public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; public MyActionFilter() :this(MyStaticKernel.Get<IMyService>()) //using Ninject, but would apply to any container { } public MyActionFilter(IMyService myService) { _myService = myService; } public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } }
这里我们使用构造函数注入并重载构造函数来使用容器并注入服务。 我确实同意将容器与ActionFilter紧密结合。
我的问题是这样的:现在在ASP.NET MVC 3,我们有一个抽象的容器正在使用(通过DependencyResolver)所有这些箍仍然是必要的? 请允许我certificate:
public class MyActionFilter : ActionFilterAttribute { private IMyService _myService; public MyActionFilter() :this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService) { } public MyActionFilter(IMyService myService) { _myService = myService; } public override void OnActionExecuting(ActionExecutingContext filterContext) { _myService.DoSomething(); base.OnActionExecuting(filterContext); } }
现在我知道一些纯粹主义者可能会嘲笑这个,但是严重的是,这会有什么坏处呢? 它仍然是可testing的,因为您可以使用在testing时使用IMyService的构造函数,并以这种方式注入模拟服务。 由于使用了DependencyResolver,所以你不会被束缚于任何DI容器的实现,所以这种方法有什么缺点吗?
顺便提一下,下面是使用新的IFilterProvider接口在MVC3中执行此操作的另一个好方法: http : //www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -Asp净MVC -3-
我不积极,但我相信你可以只使用一个空的构造函数( 属性部分),然后有一个实际注入的值( filter部分)的构造函数。*
编辑 :稍微阅读后,似乎可以接受的方式是通过属性注入:
public class MyActionFilter : ActionFilterAttribute { [Injected] public IMyService MyService {get;set;} public override void OnActionExecuting(ActionExecutingContext filterContext) { MyService.DoSomething(); base.OnActionExecuting(filterContext); } }
关于为什么不使用服务定位器的问题:它大多减less了dependency injection的灵活性。 例如,如果你正在注入一个日志服务,你想自动给日志服务注入它所在的类的名字? 如果你使用构造函数注入,那会很好。 如果您使用依赖关系parsing器/服务定位器,那么您将不会运气。
更新
既然这被接受为答案,我想留下logging,说我更喜欢Mark Seeman的方法,因为它将Action Filter的责任从属性中分离出来。 此外,Ninject的MVC3扩展有一些非常强大的方法来通过绑定configuration动作filter。 请参阅以下参考资料了解更多详情:
- https://github.com/ninject/ninject.web.mvc/wiki/Dependency-injection-for-filters
- https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters
- https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
更新2
正如@usr在下面的注释中指出的那样, ActionFilterAttribute
在类被加载时被实例化,并且它们持续整个应用程序的生命周期。 如果IMyService
接口不应该是一个Singleton,那么它最终会成为一个Captive依赖关系 。 如果它的实现不是线程安全的,你可能会遇到很多的痛苦。
每当你有一个比你的class级的预期寿命短的生命周期的依赖,注入一个工厂来产生这个依赖的需求是明智的,而不是直接注入。
是的,这有缺点,因为IDependencyResolver本身存在很多问题 ,对于那些可以添加Singleton Service Locator和Bastard Injection的用户也是如此。
更好的select是将filter作为一个普通的类来实现,你可以在其中注入你想要的任何服务:
public class MyActionFilter : IActionFilter { private readonly IMyService myService; public MyActionFilter(IMyService myService) { this.myService = myService; } public void OnActionExecuting(ActionExecutingContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } public void OnActionExecuted(ActionExecutedContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } private bool ApplyBehavior(ActionExecutingContext filterContext) { // Look for a marker attribute in the filterContext or use some other rule // to determine whether or not to apply the behavior. } private bool ApplyBehavior(ActionExecutedContext filterContext) { // Same as above } }
请注意filter是如何检查filterContext以确定是否应该应用该行为。
这意味着您仍然可以使用属性来控制是否应用filter:
public class MyActionFilterAttribute : Attribute { }
但是,现在这个属性是完全惰性的。
可以使用所需的依赖关系组成filter,并将其添加到global.asax中的全局filter中:
GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));
有关此技术的更详细示例,尽pipe应用于ASP.NET Web API而不是MVC,请参阅此文章: http : //blog.ploeh.dk/2014/06/13/passive-attributes
Mark Seemannbuild议的解决scheme看起来很优雅。 但是,对于一个简单的问题来说,这很复杂 通过实现AuthorizeAttribute使用框架感觉更自然。
我的解决scheme是创build一个具有静态委托工厂的AuthorizeAttribute到在global.asax中注册的服务。 它适用于任何DI容器,感觉稍好于服务定位器。
在global.asax中:
MyAuthorizeAttribute.AuthorizeServiceFactory = () => Container.Resolve<IAuthorizeService>();
我的自定义属性类:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class MyAuthorizeAttribute : AuthorizeAttribute { public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } }