c# asp.net-mvc-3 inversion-of-control castle-windsor quartz.net

c# - Castle.Windsor lifestyle dependiendo del contexto?



asp.net-mvc-3 inversion-of-control (5)

Debe utilizar el estilo de vida híbrido de castleprojectcontrib .

Un estilo de vida híbrido es uno que en realidad combina dos estilos de vida subyacentes: un estilo de vida principal y un estilo de vida secundario. El estilo de vida híbrido primero trata de usar el estilo de vida principal; Si no está disponible por alguna razón, usa el estilo de vida secundario. Esto se usa comúnmente con PerWebRequest como el estilo de vida principal: si el contexto HTTP está disponible, se usa como el alcance para la instancia del componente; De lo contrario se utiliza el estilo de vida secundario.

Tengo una aplicación web donde se registran muchos componentes utilizando .LifestylePerWebRequest() , ahora he decidido implementar Quartz.NET , una biblioteca de programación de trabajos .NET, que se ejecuta en subprocesos separados, y no el subproceso de solicitud.

Como tal, HttpContext.Current produce null . Mis servicios, repositorios e IDbConnection se IDbConnection instancia utilizando .LifestylePerWebRequest() porque facilitó su eliminación cuando finalizaron las solicitudes.

Ahora quiero usar estos componentes en ambos escenarios, durante las solicitudes web quiero que no se vean afectados, y en contextos sin solicitud quiero que utilicen un estilo de vida diferente, creo que puedo manejar la eliminación por mí mismo, pero ¿cómo debo ir? ¿Al respecto para elegir un estilo de vida para los componentes basados ​​en el contexto actual?

Actualmente registro servicios (por ejemplo), como este:

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

Me imagino que debería estar usando algún tipo de método de extensión pero simplemente no lo veo ...


No sé qué está pasando entre bastidores en .LifestylePerWebRequest() ; pero esto es lo que hago para los escenarios de "Contexto por solicitud":

Compruebe HttpContext para sesión y, si existe, extraiga el contexto de .Items . Si no existe, extraiga su contexto de System.Threading.Thread.CurrentContext .

Espero que esto ayude.


No utilice los mismos componentes. De hecho, en la mayoría de los escenarios, he visto que el "procesamiento en segundo plano" ni siquiera tiene sentido estar en el proceso web para empezar.

Elaboración basada en los comentarios.

El procesamiento en segundo plano de Shoehorning en la canalización web está comprometiendo su arquitectura para ahorrar unos $ en una instancia de EC2. Recomiendo encarecidamente que piense en esto de nuevo, pero estoy divagando.

Mis declaraciones siguen en pie, incluso si está colocando ambos componentes en el proceso web, son dos componentes diferentes utilizados en dos contextos diferentes y deben tratarse como tales.


Ok, ¡descubrí una manera muy limpia de hacer esto!

En primer lugar, necesitaremos una implementación de IHandlerSelector , esto puede seleccionar un manejador en función de nuestra opinión sobre el tema, o permanecer neutral (devolviendo el null , lo que significa "no hay opinión").

/// <summary> /// Emits an opinion about a component''s lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle. /// </summary> 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. } }

Lo hice así que la opinión es muy limitada a propósito. Tendré una opinión solo si hay exactamente dos manejadores y uno de ellos tiene el PerWebRequest vida PerWebRequest ; lo que significa que la otra es probablemente la alternativa no HttpContext.

Necesitamos registrar este selector con el castillo. Lo hago antes de comenzar a registrar cualquier otro componente:

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

Por último, me gustaría tener alguna idea de cómo podría copiar mi registro para evitar esto:

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

Si puede encontrar una manera de clonar un registro, cambie el estilo de vida y registre ambos (usando cualquiera de los container.Register IRegistration.Register o IRegistration.Register . IRegistration.Register ), IRegistration.Register aquí como respuesta. :)

Actualización: En las pruebas, necesito nombrar de forma única los registros idénticos, lo hice así:

.NamedRandomly() public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> 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()); }


Recientemente tuve un problema muy similar: quería poder ejecutar un código de inicialización basado en mi contenedor en el inicio de la aplicación, cuando HttpContext.Request aún no existe. No encontré ninguna forma de hacerlo, así que modifiqué la fuente de PerWebRequestLifestyleModule para permitirme hacer lo que quería. Desafortunadamente, no parecía posible realizar este cambio sin compilar Windsor. Esperaba poder hacerlo de forma extensible para poder seguir utilizando la distribución principal de Windsor.

De todos modos, para hacer que esto funcione, modifiqué la función GetScope del PerWebRequestLifestyleModule para que si NO se estaba ejecutando en un HttpContext (o si HttpContext.Request lanza una excepción, como lo hace en Application_Start), buscará un Ámbito desde donde comenzó. el contenedor en su lugar. Esto me permite usar mi contenedor en Application_Start usando el siguiente código:

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

No hay que preocuparse por la eliminación explícita de las cosas, ya que se eliminarán cuando el Ámbito de aplicación lo sea.

He puesto el código completo para el módulo de abajo. Tuve que mezclar un par de otras cosas dentro de esta clase para que funcione, pero es esencialmente lo mismo.

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); } } /// <summary> /// 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. /// </summary> /// <returns></returns> 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("<add name=/"PerRequestLifestyle/" type=/"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor/" />"); message.AppendLine("to the <httpModules> 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 <modules> section under <system.webServer>."); } else { message.AppendLine( "If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>."); } #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; } }