simpleinjector simple injector injection framework example dependency control container c# dependency-injection inversion-of-control simple-injector

c# - injection - Estilo de vida mixto para Per Thread y Per Web Request with Simple Injector



simple injector container (1)

Parece que cuando ejecuto una tarea en un hilo separado en ASP.NET MVC, SimpleInjector crea una nueva instancia de DbContext para cada llamada.

El comportamiento del estilo de vida RegisterPerWebRequest de Simple Injector v1.5 y siguientes es devolver una instancia transitoria cuando las instancias se solicitan fuera del contexto de una solicitud web (donde HttpContext.Current es nulo). Devolver una instancia transitoria fue una falla de diseño en Simple Injector, ya que esto hace que sea fácil ocultar el uso incorrecto. La versión 1.6 del inyector simple generará una excepción en lugar de devolver incorrectamente una instancia transitoria, para comunicar claramente que ha configurado incorrectamente el contenedor.

Si bien algunas bibliotecas de IoC (por ejemplo, StructureMap) tienen un estilo de vida mixto por subproceso por búsqueda de hilos, parece que Simple Injector no tiene una

Es correcto que Simple Injector no tiene soporte integrado para estilos de vida mixtos debido a un par de razones. En primer lugar, es una característica bastante exótica que no muchas personas necesitan. En segundo lugar, puede mezclar dos o tres estilos de vida juntos, por lo que sería casi una combinación interminable de híbridos. Y por último, es (bastante) fácil registrar esto usted mismo.

Aunque puede mezclar Por solicitud web con estilos de vida Per Thread , probablemente sería mejor cuando mezcle Per Web Request con Per Lifetime Scope , ya que con Lifetime Scope inicia y finaliza explícitamente el alcance (y puede eliminar el DbContext cuando finaliza el alcance) )

Desde Simple Injector 2 y posteriores , puede mezclar fácilmente cualquier cantidad de estilos de vida utilizando el método Lifestyle.CreateHybrid . Aquí hay un ejemplo:

var hybridLifestyle = Lifestyle.CreateHybrid( () => HttpContext.Current != null, new WebRequestLifestyle(), new LifetimeScopeLifestyle()); // Register as hybrid PerWebRequest / PerLifetimeScope. container.Register<DbContext, MyDbContext>(hybridLifestyle);

Hay otra pregunta de Stackoverflow que entra en este tema un poco más profundo, es posible que desee echar un vistazo: Inyector simple: multi-threading en MVC3 ASP.NET

ACTUALIZAR

Acerca de su actualización Ya casi has llegado. Los comandos que se ejecutan en un subproceso en segundo plano deben ejecutarse dentro de un alcance de por vida , por lo que tendrá que iniciarlo explícitamente. El truco aquí es llamar a BeginLifetimeScope en el nuevo hilo, pero antes de que se BeginLifetimeScope el controlador de comando real (y sus dependencias). En otras palabras, la mejor manera de hacerlo es dentro de un decorador.

La solución más fácil es actualizar su AsyncCommandHandlerDecorator para agregar el alcance:

public class AsyncCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Container _container; private readonly Func<ICommandHandler<TCommand>> _factory; public AsyncCommandHandlerDecorator(Container container, Func<ICommandHandler<TCommand>> factory) { _container = container; _factory = factory; } public void Handle(TCommand command) { ThreadPool.QueueUserWorkItem(_ => { using (_container.BeginLifetimeScope()) { // Create new handler in this thread // and inside the lifetime scope. var handler = _factory(); handler.Handle(command); } }); } }

Los puristas que abogan por los principios SÓLIDOS gritarán que esta clase está violando el Principio de Responsabilidad Individual , ya que este decorador ejecuta comandos en un nuevo hilo y comienza un nuevo alcance de por vida. No me preocuparía mucho por esto, ya que creo que existe una estrecha relación entre el inicio de un hilo de fondo y el inicio de un alcance de por vida (no usarías uno sin el otro de todos modos). Pero aún así, podría dejar intacto el AsyncCommandHandlerDecorator y crear un nuevo LifetimeScopedCommandHandlerDecorator siguiente manera:

public class LifetimeScopedCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Container _container; private readonly Func<ICommandHandler<TCommand>> _factory; public LifetimeScopedCommandHandlerDecorator(Container container, Func<ICommandHandler<TCommand>> factory) { _container = container; _factory = factory; } public void Handle(TCommand command) { using (_container.BeginLifetimeScope()) { // The handler must be created inside the lifetime scope. var handler = _factory(); handler.Handle(command); } } }

El orden en que se registran estos decoradores es, por supuesto, esencial, ya que AsyncCommandHandlerDecorator debe envolver LifetimeScopedCommandHandlerDecorator . Esto significa que el registro de LifetimeScopedCommandHandlerDecorator debe ser lo primero:

container.RegisterDecorator(typeof(ICommandHandler<>), typeof(LifetimeScopedCommandHandlerDecorator<>), backgroundCommandCondition); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(AsyncCommandHandlerDecorator<>), backgroundCommandCondition);

Esta vieja pregunta de Stackoverflow habla de esto con más detalle. Definitivamente deberías echarle un vistazo.

Estoy usando SimpleInjector como mi biblioteca de IoC. Registro DbContext según la solicitud web y funciona bien. Pero hay una tarea que ejecutar en un hilo de fondo. Entonces, tengo un problema para crear instancias de DbContext . p.ej

  1. Service1 tiene una instancia de DbContext
  2. Service2 tiene una instancia de DbContext
  3. Service1 y Service2 ejecutan desde el hilo de fondo.
  4. Service1 recupera una entidad y la pasa a Service2
  5. Service2 usa esa entidad, pero la entidad está separada de DbContext

En realidad, el problema está aquí: Service1.DbContext es la diferencia de Service2.DbContext .

Parece que cuando ejecuto una tarea en un hilo separado en ASP.NET MVC, SimpleInjector crea una nueva instancia de DbContext para cada llamada. Mientras que algunas bibliotecas de IoC (por ejemplo, StructureMap ) tienen un estilo de vida mixto por subproceso por cada búsqueda, parece que SimpleInjector no tiene una. ¿Estoy en lo cierto?

¿Tienes alguna idea de resolver este problema en SimpleInjector ? Gracias por adelantado.

EDITAR:

Mis servicios estan aqui

class Service1 : IService1 { public Service1(MyDbContext context) { } } class Service2 : IService2 { public Service2(MyDbContext context, IService1 service1) { } } class SyncServiceUsage { public SyncServiceUsage(Service2 service2) { // use Service2 (and Service1 and DbContext) from HttpContext.Current } } class AsyncServiceUsage { public AsyncServiceUsage(Service2 service2) { // use Service2 (and Service1 and DbContext) from background thread } } public class AsyncCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand { private readonly Func<ICommandHandler<TCommand>> _factory; public AsyncCommandHandlerDecorator(Func<ICommandHandler<TCommand>> factory) { _factory = factory; } public void Handle(TCommand command) { ThreadPool.QueueUserWorkItem(_ => { // Create new handler in this thread. var handler = _factory(); handler.Handle(command); }); } } void InitializeSimpleInjector() { register AsyncCommandHandlerDecorator for services (commands actually) that starts with "Async" }

AsyncService2 veces y AsyncService2 otras veces.