使用HTTP请求生存期作用域和全局筛选器避免服务定位器反模式
本文关键字:服务 定位器 模式 筛选 请求 HTTP 生存期 作用域 全局 使用 | 更新日期: 2023-09-27 18:20:43
我有一个全局筛选器属性,需要访问每个HTTP请求注册的项:
// other ContainerBuilder stuff
builder.RegisterType<HttpDependency>().As<IHttpDependency>().InstancePerHttpRequest();
其他地方:
internal sealed class MyActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// EVIL YUCKY SERVICE LOCATOR!
var resolved = AutofacDependencyResolver.Current.RequestLifetimeScope.Resolve<IHttpDependency>();
if (resolved.NeedsRedirect)
{
// does a redirect
}
base.OnActionExecuting(filterContext);
}
}
并将其注册为全局过滤器:
// in FilterConfig.cs
filters.Add(new MyActionFilter());
由于这是一个全局过滤器,我不能使用构造函数注入,即应用程序启动时的HTTP上下文不应该被每个请求重用。我如何才能在不通过服务定位器伸手抓住它的情况下正确连接?
一种方法是从Attribute中删除逻辑,并在实现IActionFilter的类中实现它。然后将该类注册到容器中,以便依赖项注入能够正常工作。果园CMS使用这种方法。
public class MyCustomActionFilterAttribute : Attribute
{
}
public class MyCustomActionFilter : FilterProvider, IActionFilter
{
protected MyService Service { get; private set; }
// MyService can be injected by the container...
public MyCustomActionFilter(MyService service)
{
this.Service = service;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Check to see if the action has a matching attribute
var attributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(MyCustomActionFilterAttribute), true);
// Perform some logic here....
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
可以创建一个将筛选器应用于操作的IActionInvoker,该类将使用DependencyResolver自动实例化我的MVC。
public class FilterResolvingActionInvoker : ControllerActionInvoker
{
protected IEnumerable<IFilterProvider> Providers { get; private set; }
// Filters registered with the container are injected by the container
public FilterResolvingActionInvoker(IEnumerable<IFilterProvider> providers)
{
this.Providers = providers;
}
// Add the filter to the current FilterInfo
protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var provider in this.Providers)
{
provider.AddFilters(filters);
}
return filters;
}
}
定义一个通用接口,允许我们注册过滤器。
public interface IFilterProvider
{
void AddFilters(FilterInfo filterInfo);
}
public abstract class FilterProvider : IFilterProvider
{
public void AddFilters(FilterInfo filterInfo)
{
if (this is IActionFilter)
{
filterInfo.ActionFilters.Add(this as IActionFilter);
}
}
}
并在容器生成器中注册它们。也可以为Autofac创建一个扩展方法,以自动注册程序集中的所有IFilterProviders。
builder.RegisterType<FilterResolvingActionInvoker>().As<IActionInvoker>().InstancePerDependency();
builder.RegisterType<MyCustomActionFilter>().As<IFilterProvider>().InstancePerDependency();
一如既往,避免定位器的选项之一是在Compositon Root中设置本地工厂。工厂的设置是为了使用你的ioc容器。
http://netpl.blogspot.com/2012/12/di-factories-and-composition-root.html
尽管您可能会争辩说,从"技术上"来说,这个"看起来像"定位器(您创建了一个工厂实例并向它请求服务),它不会向任何其他基础设施引入任何依赖关系,包括您最终用于实现工厂的实际IoC容器——实际工厂的实现是CompositionRoot的一部分(位于全局应用程序类附近)。
这种方法导致许多孤立的小工厂负责您基础设施的部分,但每个工厂仍然有一个可插入的提供程序,您可以在Composition Root附近实现,从而避免任何外部依赖。