使用StructureMapFilterProvider

时间:2016-07-13 03:20:32

标签: c# dependency-injection asp.net-mvc-5 structuremap

更新2

<击> 再次感谢您提供更详细的信息。我无法使你的GlobalFilterProvider正常工作,但似乎无论我采用哪种方法,我都无法利用嵌套容器可用的生命周期范围。目前,使用StructureMap.MVC5 NuGet包,在Application_BeginRequest上创建一个嵌套容器,并在Application_EndRequest上处理,这允许我将对象范围限定为UniquePerRequest,并在嵌套容器被放置时将它们拆除。似乎无论在Application_Start上需要添加什么过滤器,仍然需要使用容器添加任何依赖项,而Application_Start上的容器当时只是父项。在这种情况下,我甚至不认为使用Decoraptor会对我有帮助。

有没有办法实现这个目标?

此问题已解决 - 请参阅我的other SO question

更新1 - 关于NightOwl888答案的评论

我理解您链接到的“填充Setter的一个对象”部分中发生了什么,因为该代码是StructureMapFilterProvider正在做的用于构建动作过滤器的依赖关系的内容。我想要了解的是IContainer中的StructureMapFilterProvider引用。在MVC中设置的当前DependencyResolver是在解析IContainer引用吗?我只是想了解它的来源。此外,添加.Singleton()确实解决了问题,所以感谢你指出这一点。

我仍然试图了解您链接的GlobalFilterProvider,并且我确实理解使用构造函数注入的好处。我只是试着把头包裹起来。

原帖

我正在使用StructureMap来满足我的DI需求,并通过StructureMap.MVC5 NuGet包将它包含在我的ASP.NET MVC 5项目中。我有一个典型的日志记录操作过滤器,我想在我创建的动作过滤器中注入一个依赖项(ILogger)。虽然我已经找到并理解绕过二传手注射的passive attributes方法,但我也对使用StructureMapFilterProvider这样的方法很感兴趣...

public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
    private readonly IContainer _container;

    public StructureMapFilterProvider(IContainer container)
    {
        _container = container;
    }

    public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var filters = base.GetFilters(controllerContext, actionDescriptor);

        foreach (var filter in filters)
        {
            _container.BuildUp(filter.Instance);
        }

        return filters;
    }
}

然而,IContainer的{​​{1}}依赖关系似乎有点神奇。我的第一个问题是如何解决它?如果容器本身是需要解决的问题,那么解决的是什么。其次,StructureMapFilterProvider设置在StructureMap.MVC5Application_BeginRequest处为每个请求创建和处理嵌套容器时似乎存在问题。

Application_EndRequest上,过滤器提供程序工作正常,可能是因为它还不是嵌套容器,并且已解析的Application_Start实例如下所示:

enter image description here

然而,如果我在启动应用程序时再次运行相同的操作,则过滤器提供程序炸弹和IContainer实例现在看起来像这样:

enter image description here

我做错了什么?有没有解决这个问题?

如果有帮助,我的其余代码就在下面。

记录器实施:

IContainer

记录器操作过滤器:

public class Logger : ILogger
{
    public void Log(string message)
    {
        Debug.WriteLine(message);
    }
}

public interface ILogger
{
    void Log(string message);
}

其他DI设置添加到StructureMap.MVC5包提供的DependencyResolution / IoC.cs类中:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class LoggerAttribute : ActionFilterAttribute
{
    public ILogger Logger { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Logger.Log("Testing: Executing an action");
    }
}

控制器:

public static class IoC
{
    public static IContainer Initialize()
    {
        var container = new Container(c =>
        {
            c.AddRegistry<DefaultRegistry>();
            c.For<IFilterProvider>().Use<StructureMapFilterProvider>();
            c.For<ILogger>().Use<Logger>();
            c.Policies.SetAllProperties(p =>
            {
                p.OfType<ILogger>();
            });
        });

        return container;
    }
}

1 个答案:

答案 0 :(得分:1)

Setter Injection

没有魔力。其他DI包攻击MVC框架以提供FilterAttribute子类的 setter injection FilterAttribute通常由FilterAttributeFilterProvider加载,但在这种情况下,它由StructureMapFilterProvider子类化,以便提供一个扩展点来注入MVC​​ FilterAttributes

查看&#34;填充对象的设置者&#34;在StructureMap Setter Injection Documentation中 - 这基本上就是你可以在没有属性的情况下使用setter注入的方式。您只需要一个容器注册:

x.For<IGateway>().Use(theGateway);
x.Policies.SetAllProperties(y => y.OfType<IGateway>());

并调用容器:

var target = new BuildUpTarget1();
container.BuildUp(target);

IMO是使用setter注入的最佳方式,因为这意味着您不必引用StructureMap以使用其.NET属性。

  

我可能错了,但the post you linked to中的代码可能已被破坏,因为他们没有从MVC的FilterAttributeFilterProvider静态属性中删除默认FilterProviders.Providers并且他们正在添加StructureMapFilterProvider以及(通过DI),我相信它会将属性注册两次(一次使用容器依赖项,一次使用null依赖项)。

构造函数注入

但是,构造函数注入是在应用程序中使用DI的更好,更一致的方法。 IMO,如果构造函数注入是可能的永远不会使用其他选项,在这种情况下,肯定是

可以在Mark Seemann的Passive Attributes post中使用一些改进的一件事是控制全局注册过滤器的生命周期的能力,因此您可以将非单体注入过滤器(想想DbContext )。如果在GlobalFilters.Filters静态集合中注册过滤器,则所有依赖项都是静态单例的captive dependencies,这使它们按照定义单例化。事实证明使用custom filter provider很容易解决这个问题。但是,我们可以从我们的过滤器类中拆分属性类(如果需要),而只是直接从容器中解析过滤器的依赖关系图,而不是使用属性注入构建。

你有它 - 在组合根中注册MVC的单个基础架构组件以及过滤器(及其依赖项)的注册,然后是新的过滤器&#34;只是工作&#34;一旦他们注册。他们甚至不关心他们的依赖来自哪里。注册期间没有笨拙的SetAllProperties调用或使用特定于容器的.NET属性装饰属性类以进行setter注入(这反过来又要求DLL引用它们不属于的地方)。无需在FilterAttributes上创建公共属性,这些属性神秘地填充了依赖项(或不是)。使用适当的保护子句,无需担心null setter注入的依赖项。这是在你的应用程序的 part 中使用setter注入的首要问题,因为你太懒了,无法为MVC添加适当的扩展以便使用构造函数注入,而且懒得通过分离服务来跟踪SRP (过滤)来自元数据(.NET属性)并在需要注入依赖项时将FilterAttribute及其所有衍生物抛出窗口。

  

我在应用程序启动时再次运行相同的操作,过滤器提供程序炸弹

     

我做错了什么?有没有解决这个问题?

由于过滤器提供程序是通过DI注册的,其生存期为Per Request(默认值),因此每次请求都会对其进行实例化。我打赌你在构建容器后没有静态访问容器,所以你得到错误,因为容器在应用程序启动后超出了范围。

如果您将生命周期更改为单例(或通过静态FilterProviders.Providers属性注册它),它将保留对全局DI容器俘虏的引用,因此它不会超出范围。

c.For<IFilterProvider>().Singleton().Use<StructureMapFilterProvider>();
  

我想要了解的是IContainer中的StructureMapFilterProvider引用。在MVC中设置的当前DependencyResolver是在解析IContainer引用吗?

实际上,如果在调用DependencyResolver.SetResolver之前放置断点,然后在即时窗口中运行以下行,则可以看到StructureMap在中注册了一个本身的实例。容器。

container.GetInstance<IContainer>()

您可以通过尝试解析已解析容器中注册的其中一种类型来证明它。

container.GetInstance<IContainer>().GetInstance<ILogger>()

因此,StructureMap正在展示这种行为。我使用过的大多数其他DI容器都没有像这样自行注册(但可以手动完成)。

显然,自我注册的IContainer实例未注册为单例。因此,正如我之前提到的,您注入IContainer的任何基础架构组件应该是单例。如果不是,它将失去对IContainer的引用。我不建议你尝试使用IContainer作为单身人士,因为必须有一个很好的理由说明为什么它不能自我注册。

  

我仍然试图了解您链接到

的GlobalFilterProvider

实际上,这是完全相同的概念。但在这种情况下,我决定向DI容器注册IDependencyResolver,因此可以用它来为基础架构组件提供服务。

// Register other container components...

// Create Dependency Resolver
var dependencyResolver = new StructureMapDependencyResolver(container);

// Register the dependency resolver with StructureMap
For<IDependencyResolver>().Use(dependencyResolver);

// Set the dependency resolver for MVC
DependencyResolver.SetResolver(dependencyResolver);

在此示例中注入IDependencyResolver的原因是什么。

好的,从表面上看,这看起来是不必要的,因为只需将IContainer注入基础架构组件,就可以自动获取对容器的引用。但是使用IDependencyResolver(MVC的一部分)可以更容易切换到另一个DI容器,如果你以后决定你选择的那个并不像新的闪亮DI容器那么好你刚刚在一些博客上看过。更不用说,对于那些可能不知道如何实现特定于他们的 DI容器的人来说,对StackOverflow上的人更有用 - 一个通用的更适合用于信息目的。

在我看来,你依赖 DI容器越少,如果你需要更换容器就行更安全(这是另一个反对使用setter注入的论据,因为每个DI容器做的有点不同)。此外,使用IDependencyResolver(它是MVC的一部分)清楚地将组件描述为 MVC基础架构组件,它仅用作应用程序和MVC之间的代理。这个组件应该被认为是组合根和IMO的一部分,这意味着它的实现应该是MVC项目的一部分,而不是在某个地方养成DLL。但在这种情况下,它也可以被视为MVC扩展代码的一部分,这意味着您可以将它放在放置其他MVC扩展的位置而不必过多担心组合根部分。当您更改组合根时,它不需要更改。

尽管如此,这种方法还有一个缺点。它强制 MVC应用程序使用IDependencyResolver而不是IControllerFactory作为DI集成点。如果您将IContainer注入组件,则可以自由选择使用IControllerFactory