Castle.Windsor生活方式取决于具体情况?

我有一个Web应用程序,其中许多组件使用.LifestylePerWebRequest()注册,现在我决定实现Quartz.NET ,一个.NET作业调度库,它在不同的线程中执行,而不是Request线程。

因此, HttpContext.Current产生null 。 到目前为止,我的服务,存储库和IDbConnection使用.LifestylePerWebRequest()实例化,因为它使得在请求结束时更容易处理它们。

现在我想在两种情况下使用这些组件,在Web请求期间我希望它们不受影响,在非请求上下文中我希望它们使用不同的生活方式,我想我可以自己处理处理,但我该怎么办关于它根据当前背景选择组件的生活方式?

目前我注册服务(例如),像这样:

 container.Register( AllTypes .FromAssemblyContaining() .Where(t => t.Name.EndsWith("Service")) .WithService.Select(IoC.SelectByInterfaceConvention) .LifestylePerWebRequest() ); 

我想我应该使用某种扩展方法,但我只是看不到它..

您应该使用来自castleprojectcontrib的 Hybrid Lifestyle 。

混合生活方式实际上融合了两种潜在的生活方式:主要生活方式和次要生活方式。 混合生活方式首先尝试使用主要的生活方式; 如果由于某种原因它不可用,它会使用次要的生活方式。 这通常与PerWebRequest一起用作主要生活方式:如果HTTP上下文可用,则将其用作组件实例的范围; 否则使用次要生活方式。

不要使用相同的组件。 事实上,在大多数情况下,我已经看到“后台处理”在网络流程中开始时甚至没有意义。

根据评论进行阐述。

Web管道中的Shoehorning后台处理会损害您的体系结构,以便在EC2实例上节省一些$。 我强烈建议再考虑一下,但我离题了。

我的陈述仍然有效,即使你将两个组件都放在Web过程中,它们是两个不同的上下文中使用的两个不同的组件,应该这样对待。

我最近遇到了一个非常类似的问题 – 当HttpContext.Request尚不存在时,我希望能够在Application启动时根据我的容器运行初始化代码。 我没有找到任何方法,所以我修改了PerWebRequestLifestyleModule的源代码以允许我做我想要的。 不幸的是,如果不重新编译Windsor,似乎不可能做出这种改变 – 我希望我能够以可扩展的方式做到这一点,所以我可以继续使用Windsor的主要发行版。

无论如何,为了使这项工作,我修改了GetScope函数,这样如果它没有在HttpContext中运行(或者如果HttpContext.Request抛出exception,就像它在Application_Start中那样)那么它将寻找一个从而是容器。 这允许我使用以下代码在Application_Start中使用我的容器:

 using (var scope = container.BeginScope()) { // LifestylePerWebRequest components will now be scoped to this explicit scope instead // _container.Resolve<...>() } 

没有必要担心明确处理事物,因为它们将在范围内被处置。

我已经为下面的模块提供了完整的代码。 我不得不在这个课程中改变其他一些东西以使其工作,但它基本上是相同的。

 public class PerWebRequestLifestyleModule : IHttpModule { private const string key = "castle.per-web-request-lifestyle-cache"; private static bool allowDefaultScopeOutOfHttpContext = true; private static bool initialized; public void Dispose() { } public void Init(HttpApplication context) { initialized = true; context.EndRequest += Application_EndRequest; } protected void Application_EndRequest(Object sender, EventArgs e) { var application = (HttpApplication)sender; var scope = GetScope(application.Context, createIfNotPresent: false); if (scope != null) { scope.Dispose(); } } private static bool IsRequestAvailable() { if (HttpContext.Current == null) { return false; } try { if (HttpContext.Current.Request == null) { return false; } return true; } catch (HttpException) { return false; } } internal static ILifetimeScope GetScope() { var context = HttpContext.Current; if (initialized) { return GetScope(context, createIfNotPresent: true); } else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable()) { // We're not running within a Http Request. If the option has been set to allow a normal scope to // be used in this situation, we'll use that instead ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope(); if (scope == null) { throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()"); } return scope; } else if (context == null) { throw new InvalidOperationException( "HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net"); } else { EnsureInitialized(); return GetScope(context, createIfNotPresent: true); } } ///  /// Returns current request's scope and detaches it from the request context. /// Does not throw if scope or context not present. To be used for disposing of the context. ///  ///  internal static ILifetimeScope YieldScope() { var context = HttpContext.Current; if (context == null) { return null; } var scope = GetScope(context, createIfNotPresent: true); if (scope != null) { context.Items.Remove(key); } return scope; } private static void EnsureInitialized() { if (initialized) { return; } var message = new StringBuilder(); message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName); message.AppendLine("To fix this add"); message.AppendLine(""); message.AppendLine("to the  section on your web.config."); if (HttpRuntime.UsingIntegratedPipeline) { message.AppendLine( "Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the  section under ."); } else { message.AppendLine( "If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the  section under ."); } #if !DOTNET35 message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll + " assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file."); #endif throw new ComponentResolutionException(message.ToString()); } private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent) { var candidates = (ILifetimeScope)context.Items[key]; if (candidates == null && createIfNotPresent) { candidates = new DefaultLifetimeScope(new ScopeCache()); context.Items[key] = candidates; } return candidates; } } 

好的,我想出了一个非常干净的方法来做到这一点!

首先,我们需要一个IHandlerSelector的实现,这可以根据我们对此事的看法选择一个处理程序,或者保持中立(通过返回null ,这意味着“没有意见”)。

 ///  /// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle. ///  public class LifestyleSelector : IHandlerSelector { public bool HasOpinionAbout(string key, Type service) { return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null. } public IHandler SelectHandler(string key, Type service, IHandler[] handlers) { if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest)) { if (HttpContext.Current == null) { return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest); } else { return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest); } } return null; // we don't have an opinion in this case. } } 

我这么做是因为意见非常有限。 只有当有两个处理程序并且其中一个具有PerWebRequest生活方式时,我才会有意见; 意思是另一个可能是非HttpContext替代方案。

我们需要在Castle中注册这个选择器。 我在开始注册任何其他组件之前这样做:

 container.Kernel.AddHandlerSelector(new LifestyleSelector()); 

最后,我希望我有任何线索可以复制我的注册以避免这种情况:

 container.Register( AllTypes .FromAssemblyContaining() .Where(t => t.Name.EndsWith("Service")) .WithService.Select(IoC.SelectByInterfaceConvention) .LifestylePerWebRequest() ); container.Register( AllTypes .FromAssemblyContaining() .Where(t => t.Name.EndsWith("Service")) .WithService.Select(IoC.SelectByInterfaceConvention) .LifestylePerThread() ); 

如果您可以找到克隆注册的方法,请更改生活方式并注册它们(使用container.RegisterIRegistration.Register ),请在此处将其作为答案发布! 🙂

更新:在测试中,我需要唯一地命名相同的注册,我这样做:

 .NamedRandomly() public static ComponentRegistration NamedRandomly(this ComponentRegistration registration) where T : class { string name = registration.Implementation.FullName; string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid()); return registration.Named(random); } public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration) { return registration.Configure(x => x.NamedRandomly()); } 

我不知道.LifestylePerWebRequest()幕后发生了什么。 但这就是我为“每个请求的上下文”场景所做的事情:

检查HttpContext的会话,如果存在则从.Items提取上下文。 如果它不存在,请从System.Threading.Thread.CurrentContext提取您的上下文。

希望这可以帮助。