将依赖项注入ASP.. NET MVC 3动作过滤器.这种方法有什么问题?
本文关键字:过滤器 方法 问题 什么 注入 依赖 ASP NET MVC | 更新日期: 2023-09-27 18:06:34
设置如下。假设我有一个需要服务实例的动作过滤器:
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中,将依赖项注入到动作过滤器中有点麻烦。最常见的方法是使用自定义动作调用程序,如下所示: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)所有这些箍仍然是必要的吗?请允许我演示一下:
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);
}
}
现在我知道一些纯粹主义者可能会嘲笑这一点,但说真的,会有什么缺点呢?它仍然是可测试的,因为您可以使用在测试时接受IMyService的构造函数,并以这种方式注入模拟服务。由于您使用的是DependencyResolver,因此您没有被束缚于任何DI容器的实现,那么这种方法有什么缺点吗?
顺便说一下,这里有另一种很好的方法在MVC3中使用新的IFilterProvider接口:http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3
是的,有缺点,因为有很多问题与IDependencyResolver本身,和那些你可以添加使用单例服务定位器,以及Bastard注入。
一个更好的选择是将过滤器实现为一个普通类,你可以向其中注入任何你想要的服务:
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
}
}
请注意过滤器是如何检查filterContext以确定是否应该应用该行为的。
这意味着您仍然可以使用属性来控制是否应该应用过滤器:
public class MyActionFilterAttribute : Attribute { }
但是,现在这个属性是完全惰性的。
过滤器可以由所需的依赖项组成,并添加到global.asax:
中的全局过滤器中。GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));
对于该技术的更详细的示例,虽然应用于ASP。. NET Web API代替MVC,参见这篇文章:http://blog.ploeh.dk/2014/06/13/passive-attributes
我不是很肯定,但我相信你可以只使用一个空的构造函数(对于属性部分),然后有一个构造函数实际注入值(对于过滤器部分)。
编辑:经过一番阅读,似乎可以接受的方式是通过属性注入:
public class MyActionFilter : ActionFilterAttribute
{
[Injected]
public IMyService MyService {get;set;}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MyService.DoSomething();
base.OnActionExecuting(filterContext);
}
}
关于为什么不使用服务定位器的问题:它只会降低依赖注入的灵活性。例如,如果您正在注入一个日志服务,并且希望自动为日志服务提供被注入的类的名称,该怎么办?如果使用构造函数注入,效果会很好。如果你使用的是依赖解析器/服务定位器,那你就不走运了。
更新既然这个答案被接受了,我想公开地说,我更喜欢Mark Seeman的方法,因为它将动作过滤器的职责从属性中分离出来。此外,Ninject的MVC3扩展有一些非常强大的方法来通过绑定配置动作过滤器。有关详细信息,请参阅以下参考资料:
- 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
接口不应该是单例的,那么它最终会成为一个强制依赖。如果它的实现不是线程安全的,你可能会很痛苦。
当你有一个依赖的寿命比你的类的预期寿命短的时候,明智的做法是注入一个工厂来按需生产这个依赖,而不是直接注入。
Mark Seemann提出的解决方案似乎很优雅。然而,对于一个简单的问题来说,这是相当复杂的。通过实现AuthorizeAttribute来使用框架感觉更自然。
我的解决方案是创建一个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);
}
}