被动属性和嵌套容器
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_BeginRequest
和 App_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>();
最终解决方案
在@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_BeginRequest
和 App_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>();