使用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上下文不应该被每个请求重用。我如何才能在不通过服务定位器伸手抓住它的情况下正确连接?

使用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附近实现,从而避免任何外部依赖。