質問

My goal: To inject an IFilterProvider in the case where one is provided by DI, but to fallback to the global FilterProviders.Providers.GetFilters() method by default.

There are a lot of resources on the Internet (including an "official" Microsoft one) that demonstrate how to inject the IFilterProvider interface into a class. However, all of them use the service locator anti-pattern (per Mark Seeman) to do so. Here is a list of the ones I found:

  1. http://msdn.microsoft.com/en-us/vs2010trainingcourse_aspnetmvcdependencyinjection_topic4.aspx
  2. http://merbla.blogspot.com/2011/02/ifilterprovider-using-autofac-for-mvc-3.html
  3. http://thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc-3
  4. IFilterProvider and separation of concerns

So what's wrong with these methods? They all inject a DI container into the target class instead of the other way around. If you see something in your class like container.Resolve(), then you are not yielding control of the lifetime of objects to the DI container, which is what inversion of control is really about.

In addition, the problem with trying to create a fallback to the global FilterProviders.Providers instance is that although it has the same signature as the IFilterProvider interface, it does not actually implement this interface. So how can the global static member be injected as a logical default and still allow the use of DI to override it?

役に立ちましたか?

解決

Create an wrapper class based on IFilterProvider that returns the global FilterProviders.Providers.GetFilters() result, like this:

public class FilterProvider
    : IFilterProvider
{
    #region IFilterProvider Members

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        return FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor);
    }

    #endregion
}

Then, adjust the constructor in your classes that require the dependency.

public class MyBusinessClass
    : IMyBusinessClass
{
    public MyBusinessClass(
        IFilterProvider filterProvider
        )
    {
        if (filterProvider == null)
            throw new ArgumentNullException("filterProvider");
        this.filterProvider = filterProvider;
    }
    protected readonly IFilterProvider filterProvider;

    public IEnumerable<AuthorizeAttribute> GetAuthorizeAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var filters = filterProvider.GetFilters(controllerContext, actionDescriptor);

        return filters
                .Where(f => typeof(AuthorizeAttribute).IsAssignableFrom(f.Instance.GetType()))
                .Select(f => f.Instance as AuthorizeAttribute);
    }
}

Next, configure your DI container to use FilterProvider concrete type as the default.

container.Configure(x => x
    .For<IFilterProvider>()
    .Use<FilterProvider>()
);

If you need to override the default and provide your own IFilterProvider, now it is just a matter of creating a new concrete type based on IFilterProvider and swapping what is registered in the DI container.

//container.Configure(x => x
//    .For<IFilterProvider>()
//    .Use<FilterProvider>()
//);

container.Configure(x => x
    .For<IFilterProvider>()
    .Use<NewFilterProvider>()
);

Most importantly, there is no service locator anywhere in this pattern.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top