动作过滤器 Setter 使用 StructureMapFilterProvider 注入

Action Filter Setter Injection with StructureMapFilterProvider

更新 2

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

有什么办法可以实现吗?

这个问题已经解决了-看我的.

更新 1 - 评论 NightOwl888 的回答

我了解您链接到的 "Filling Setter's of an Object" 片段中发生的事情,因为该代码是 StructureMapFilterProvider 为建立动作过滤器的依赖关系所做的。我试图理解的是 StructureMapFilterProvider 中的 IContainer 参考。 MVC 中设置的当前 DependencyResolver 是在解析那个 IContainer 引用吗?我只想了解那是从哪里来的。此外,添加 .Singleton() 确实解决了问题,因此感谢您指出这一点。

我仍在努力理解您链接到的 GlobalFilterProvider,我确实理解使用构造函数注入的好处。我只是想全神贯注。


原创Post

我正在使用 StructureMap 来满足我的 DI 需求,并通过 StructureMap.MVC5 NuGet 包将其包含在我的 ASP.NET MVC 5 项目中。我有一个典型的日志记录操作过滤器,我想在其中将依赖项 (ILogger) 注入到我创建的操作过滤器中。虽然我已经找到并理解了 passive attributes method for bypassing setter injection, I also became intrigued with the method of using a 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;
    }
}

不过,StructureMapFilterProviderIContainer依赖似乎有点神奇。我的第一个问题是它是如何得到解决的?如果容器本身是需要解决的问题,那么正在解决什么问题。其次,StructureMap.MVC5 安装程序在 Application_BeginRequestApplication_EndRequest 处创建和处理每个请求的嵌套容器似乎有问题。

Application_Start 上,过滤器提供程序工作正常,大概是因为它还不是嵌套容器,解析的 IContainer 实例如下所示:

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

我做错了什么?有解决办法吗?


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

记录器实现:

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

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

记录器操作过滤器:

[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");
    }
}

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

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;
    }
}

控制器:

public class HomeController : Controller
{
    [Logger]
    public ActionResult Index()
    {
        return Content("worked");
    }
}

Setter 注射

没有魔法。其他 DI 包破解 MVC 框架以提供 setter 注入 FilterAttribute subclasses。 FilterAttribute 通常由 FilterAttributeFilterProvider 加载,但在这种情况下,它由 StructureMapFilterProvider 子class 编辑,以提供注入 MVC 的扩展点 FilterAttributes.

看看 StructureMap Setter Injection Documentation 中的“填充 Setter 的对象” - 这基本上是您可以使用 setter 注入而不带属性的方式。您只需要一个容器注册:

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

以及对容器的调用:

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

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

I could be wrong, but the code in the post you linked to is likely broken because they are not removing the default FilterAttributeFilterProvider from MVC's FilterProviders.Providers static property and they are adding the StructureMapFilterProvider as well (through DI), which I believe will register the attributes twice (once with the container dependencies and once with null dependencies).

构造函数注入

但是,构造函数注入是在应用程序中使用 DI 的一种更好、更一致的方法。 IMO,如果构造函数注入 可能 ,你应该 永远不要 使用另一个选项 ,在这种情况下它 肯定是 .

Mark Seemann 的 Passive Attributes post is the ability to control lifetime in globally registered filters, so you can inject non-singletons into the filter (think DbContext). If you register the filter in the GlobalFilters.Filters static collection, all dependencies are captive dependencies of a static singleton, which makes them singleton by definition. It turns out that is pretty easy to work around this problem using a 可以改进的一件事。但是我们可以将我们的属性 classes(如果需要)从我们的过滤器 classes 中分离出来,而不是使用 属性 注入来构建,并且只解析依赖关系图 直接从容器过滤

你已经知道了 - 一个在组合根中使用 MVC 注册的单一基础结构组件以及过滤器(及其依赖项)的注册,然后新过滤器一注册就“正常工作”。他们甚至不关心他们的依赖来自哪里。在注册期间没有尴尬的 SetAllProperties 调用或使用特定于容器的 .NET 属性装饰您的属性 class 以进行 setter 注入(这反过来需要 DLL 引用它们不属于)。无需在 FilterAttributes 上创建 public 属性,这些属性会神秘地填充(或不填充)依赖项。使用适当的保护子句,无需担心 null setter 注入的依赖项。这是在应用程序的 部分 中使用 setter 注入的头肩,只是因为您懒得向 MVC 添加适当的扩展以使用构造函数注入而且太懒了通过将服务(过滤器)与元数据(.NET 属性)分开并在需要注入依赖项时将 FilterAttribute 及其所有衍生产品从 window 中分离出来来遵循 SRP。

I run the same action again now that the application is started, the filter provider bombs

What am I doing wrong? Is there a fix for this?

由于过滤器提供程序是通过 DI 注册的,其生命周期为 Per Request(默认值),因此它会在每个请求上实例化。我敢打赌,在构建容器后您无法对其进行静态访问,因此您会收到错误消息,因为容器在应用程序启动后超出了范围。

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

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

What I was trying to understand is the IContainer reference in the StructureMapFilterProvider. Is the current DependencyResolver set in MVC what is doing the resolving of that IContainer reference?

实际上,如果你在调用DependencyResolver.SetResolver之前打一个断点,然后运行紧接着的下一行window,你可以看到StructureMap注册了一个实例自身在容器中。

container.GetInstance<IContainer>()

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

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

所以是 StructureMap 表现出这种行为。我使用过的大多数其他 DI 容器都不会像这样自行注册(但可以手动进行)。

很明显,自注册的 IContainer 实例没有被注册为单例。因此,正如我之前提到的,您注入 IContainer 的任何基础结构组件都应该是 单例 。如果不是,它将失去对 IContainer 的引用。我不建议您尝试将 IContainer 用作单例,因为必须有充分的理由说明它不以这种方式自行注册。

I'm still trying to understand that GlobalFilterProvider you linked to

其实是同一个概念。但在那种情况下,我决定向 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 好您刚刚在某个博客上读到的容器。更不用说,对于 Whosebug 上的那些可能不知道如何实现特定于 他们的 DI 容器的人来说,它更有用 - 通用的更适合用于信息目的。

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

也就是说,这种方法有一个缺点。它强制 MVC 应用程序使用IDependencyResolver 而不是IControllerFactory 作为它的DI 集成点。如果您将 IContainer 注入到组件中,您可以随意使用 IControllerFactory,如果您愿意的话。