被动属性和嵌套容器

Passive Attributes and Nested Containers

最终解决方案

在@NightOwl888 的回答的帮助下,这是我为最终来到这里的任何人采用的最终方法:

1) 添加了全局过滤器提供程序:

public class GlobalFilterProvider : IFilterProvider
{
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var nestedContainer = StructuremapMvc.StructureMapDependencyScope.CurrentNestedContainer;

        foreach (var filter in nestedContainer.GetAllInstances<IActionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IAuthorizationFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IExceptionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IResultFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IAuthenticationFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
    }
}

2) 在 FilterProviders 集合中注册它:

public static void Application_Start()
{
    // other bootstrapping code...

    FilterProviders.Providers.Insert(0, new GlobalFilterProvider());
}

3) 使用 passive attributes 方法添加了自定义过滤器:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class SomeAttribute : Attribute
{
}

public class SomeFilter : IActionFilter
{
    private readonly ISomeDependency _dependency;

    public SomeFilter(ISomeDependency dependency)
    {
        _dependency = dependency;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<SomeAttribute>().Any())
            return;

        _dependency.DoWork();
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }
}

4) 然后在 StructureMap 中连接所有内容(在此解决方案中,SomeAttribute 和 GlobalFilterProvider 类 位于根文件夹内的同一 "Filters" 文件夹中):

public class ActionFilterRegistry : Registry
{
    public ActionFilterRegistry()
    {
        Scan(s =>
        {
            // find assembly containing custom filters
            s.AssemblyContainingType<GlobalFilterProvider>();

            // limit it to the folder containing custom filters
            s.IncludeNamespaceContainingType<GlobalFilterProvider>();

            // exclude any of the Attribute classes that contain metadata but not the behavior
            s.Exclude(type => type.IsSubclassOf(typeof(Attribute)));

            // register our custom filters
            s.AddAllTypesOf<IActionFilter>();
        });
    }
}

原版Post

我目前在 ASP.NET MVC 5 应用程序中使用 StructureMap 的每个请求嵌套容器。我正在利用 structuremap.mvc5 nuget 包为我设置所有 DI 基础设施(依赖解析器、连接容器以及在 App_BeginRequestApp_EndRequest 上创建和处理嵌套容器) .我现在正处于需要在操作过滤器中执行一些 DI 以自动化某些功能的地步。经过大量研究,我尝试使用 Mark Seemann 的 passive attributes 方法在不需要 setter 注入的情况下这样做。

在构建属性和过滤器时一切似乎都很好,直到我在 App_Start 中将过滤器注册到全局过滤器集合。我有一个依赖项,我希望每个请求只创建一次,这样不仅动作过滤器,而且请求期间使用的其他非过滤器基础设施 类 都可以使用该依赖项的相同实例整个请求。如果嵌套容器正在解决依赖关系,它会默认这样做。但是,因为我必须在 App_Start 中注册新过滤器,所以我无权访问嵌套容器。

比如我的global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    public static StructureMapDependencyScope StructureMapDependencyScope { get; set; }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RouteConfig.RegisterRoutes(RouteTable.Routes);

        var container = IoC.Initialize(); // contains all DI registrations
        StructureMapDependencyScope = new StructureMapDependencyScope(container);
        DependencyResolver.SetResolver(StructureMapDependencyScope);

        // filter uses constructor injection, so I have to give it an instance in order to new it up, 
        // but nested container is not available
        GlobalFilters.Filters.Add(new SomeFilter(container.GetInstance<ISomeDependency>()));
    }

    protected void Application_BeginRequest()
    {
        StructureMapDependencyScope.CreateNestedContainer();
    }

    protected void Application_EndRequest()
    {
        HttpContextLifecycle.DisposeAndClearAll();
        StructureMapDependencyScope.DisposeNestedContainer();
    }

    protected void Application_End()
    {
        StructureMapDependencyScope.Dispose();
    }
}

有人知道怎么解决吗?我也通过这个 other SO question 遇到了 decoraptor 解决方案,但是在我的过滤器中使用抽象工厂只会创建一个新的依赖实例,而不是使用我想要的每个请求实例。

我想出的唯一其他解决方案是使用 setter 注入自定义过滤器提供程序,该提供程序使用在全局中创建的静态 StructureMapDependencyScope 实例,如下所示:

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

        foreach (var filter in filters)
        {
            MvcApplication.StructureMapDependencyScope.CurrentNestedContainer.BuildUp(filter.Instance);
        }

        return filters;
    }
}

虽然这似乎工作正常,但似乎有点脏。

您可以构建自定义过滤器提供程序(如 )来控制过滤器的生命周期,而不是将它们注册到静态 GlobalFilters.Filters 集合中。

public class GlobalFilterProvider : IFilterProvider
{
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var nestedContainer = StructuremapMvc.StructureMapDependencyScope.CurrentNestedContainer;

        foreach (var filter in nestedContainer.GetAllInstances<IActionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IAuthorizationFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IExceptionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IResultFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }

        foreach (var filter in nestedContainer.GetAllInstances<IAuthenticationFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
    }
}

用法

请记住,MVC 已经包含很多过滤器类型。甚至 基本控制器类型的实现也被注册为全局过滤器 ,因为 Controller 实现了每种类型的过滤器。因此,在注册自定义全局过滤器类型时需要精确。

选项 1:使用基于约定的注册

// Register the filter provider with MVC.
FilterProviders.Providers.Insert(0, new GlobalFilterProvider());

然后在你的DI注册中

Scan(_ =>
{
    // Declare which assemblies to scan
    // In this case, I am assuming that all of your custom
    // filters are in the same assembly as the GlobalFilterProvider.
    // So, you need to adjust this if necessary.
    _.AssemblyContainingType<GlobalFilterProvider>();

    // Optional: Filter out specific MVC filter types
    _.Exclude(type => type.Name.EndsWith("Controller"));

    // Add all filter types.
    _.AddAllTypesOf<IActionFilter>();
    _.AddAllTypesOf<IAuthorizationFilter>();
    _.AddAllTypesOf<IExceptionFilter>();
    _.AddAllTypesOf<IResultFilter>();
    _.AddAllTypesOf<IAuthenticationFilter>(); // MVC 5 only
});

NOTE: You can control which filters MVC registers by changing the IFilterProvider instances that are registered.

所以,替代方案可能是这样的:

FilterProviders.Providers.Clear();

// Your custom filter provider
FilterProviders.Providers.Add(new GlobalFilterProvider());

// This provider registers any filters in GlobalFilters.Filters
FilterProviders.Providers.Add(new System.Web.Mvc.GlobalFilterCollection());

// This provider registers any FilterAttribute types automatically (such as ActionFilterAttribute)
FilterProviders.Providers.Insert(new System.Web.Mvc.FilterAttributeFilterCollection());

由于上面的代码没有注册System.Web.Mvc.ControllerInstanceFilterProvider,控制器本身不会注册为全局过滤器,所以你不需要过滤掉它们。相反,您可以简单地将所有控制器注册为全局过滤器。

// Optional: Filter out specific MVC filter types
// _.Exclude(type => type.Name.EndsWith("Controller"));

选项 2:个人注册

// Register the filter provider with MVC.
FilterProviders.Providers.Insert(0, new GlobalFilterProvider());

然后在你的DI注册中

For<IActionFilter>().Use<MyGlobalActionFilter>();
For<IActionFilter>().Use<MyOtherGlobalActionFilter>();