使用HTTP请求生命周期范围和全局过滤器避免服务定位器反模式

时间:2013-07-02 19:44:12

标签: c# asp.net-mvc-4 autofac

我有一个全局过滤器属性,需要访问按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上下文不应该被重用于每个请求。如何在不通过服务定位器伸出并抓住它的情况下正确连接它?

2 个答案:

答案 0 :(得分:1)

一种方法是从Attribute中删除逻辑,并在实现IActionFilter的类中实现它。然后在容器中注册该类,以便依赖注入将正常工作。 Orchard 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();

答案 1 :(得分:-1)

与往常一样,避免定位器的一个选项是拥有一个在Compositon Root中设置的本地工厂。工厂设置为使用您的ioc容器。

http://netpl.blogspot.com/2012/12/di-factories-and-composition-root.html

虽然您可以说“技术上”这个“看起来像”定位器(您创建一个工厂实例并要求它提供服务),但它不会向任何其他基础结构引入任何依赖关系,包括您最终使用的实际IoC容器实现工厂 - 实际工厂的实现是Composition Root的一部分(在全局应用程序类附近)。

这种方法导致许多孤立的小工厂负责部分基础架构,但每个工厂仍然有一个可插拔的提供程序,您可以在Composition Root附近实现,从而避免任何外部依赖。