被动属性和嵌套容器

时间:2016-07-20 14:18:21

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

最终解决方案

在@ 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类位于根文件夹中的#34; Filters&#34;文件夹中):

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

原帖

我目前在ASP.NET MVC 5应用程序中使用StructureMap的每个请求使用嵌套容器。我正在利用structuremap.mvc5 nuget包为我设置所有DI基础设施(依赖解析器,连接容器以及在App_BeginRequest和{{1}上创建和处理嵌套容器})。我现在需要在动作过滤器中执行一些DI,以便自动执行某些功能。经过大量的研究,我试图这样做,而不需要使用Mark Seemann的passive attributes方法进行二次注射。

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

例如,我的global.asax:

App_EndRequest

有谁知道如何解决这个问题?我也通过这个other SO question遇到了decoraptor解决方案,但是在我的过滤器中使用抽象工厂只会创建一个新的依赖实例,而不是使用我想要的单个请求实例。 / p>

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

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

虽然这似乎没有用,但它看起来有点脏。

1 个答案:

答案 0 :(得分:2)

您可以构建自定义过滤器提供程序(如this answer中所述)来控制过滤器的生命周期,而不是在静态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
});
  

注意:您可以通过更改已注册的IFilterProvider实例来控制MVC注册的过滤器。

     

enter image description here

所以,另一种选择可能是:

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>();