Estoy usando Inyector simple para administrar la vida útil de mis dependencias inyectadas (en este caso UnitOfWork
), y estoy muy contento de tener un decorador por separado en lugar de mi El servicio o controlador de comando que se ocupa de guardar y eliminar hace que el código sea mucho más fácil cuando se escriben capas de lógica de negocios (sigo la arquitectura que se describe en this blog post).Cómo configurar Inyector simple para ejecutar subprocesos de fondo en ASP.NET MVC
Lo anterior funciona perfectamente (y muy fácilmente) utilizando el paquete Simple Injector MVC NuGet y el siguiente código durante la construcción del contenedor raíz de composición, si existe más de una dependencia en el gráfico, la misma instancia se inyecta en todo perfecto para el contexto del modelo de Entity Framework.
private static void InitializeContainer(Container container)
{
container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
// register all other interfaces with:
// container.Register<Interface, Implementation>();
}
ahora tengo que hacer unos hilos de fondo y entender de inyector simple documentation on threads que los comandos se pueden proxy de la siguiente manera:
public sealed class TransactionCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> handlerToCall;
private readonly IUnitOfWork unitOfWork;
public TransactionCommandHandlerDecorator(
IUnitOfWork unitOfWork,
ICommandHandler<TCommand> decorated)
{
this.handlerToCall = decorated;
this.unitOfWork = unitOfWork;
}
public void Handle(TCommand command)
{
this.handlerToCall.Handle(command);
unitOfWork.Save();
}
}
ThreadedCommandHandlerProxy:
public class ThreadedCommandHandlerProxy<TCommand>
: ICommandHandler<TCommand>
{
Func<ICommandHandler<TCommand>> instanceCreator;
public ThreadedCommandHandlerProxy(
Func<ICommandHandler<TCommand>> creator)
{
this.instanceCreator = creator;
}
public void Handle(TCommand command)
{
Task.Factory.StartNew(() =>
{
var handler = this.instanceCreator();
handler.Handle(command);
});
}
}
Sin embargo, desde este enhebrado la documentación de la muestra que puedo ver fábricas se utilizan, si introduzco fábricas a mis comandos y la capa de servicio las cosas se confunden y no sistente como voy a tener diferentes métodos de ahorro para los distintos servicios (un contenedor se encarga de ahorro, otras fábricas instanciados dentro del mango servicios de guarda y desechar) - se puede ver cómo clara y sencilla el esqueleto código de servicio es sin ningún tipo de fábricas:
public class BusinessUnitCommandHandlers :
ICommandHandler<AddBusinessUnitCommand>,
ICommandHandler<DeleteBusinessUnitCommand>
{
private IBusinessUnitService businessUnitService;
private IInvoiceService invoiceService;
public BusinessUnitCommandHandlers(
IBusinessUnitService businessUnitService,
IInvoiceService invoiceService)
{
this.businessUnitService = businessUnitService;
this.invoiceService = invoiceService;
}
public void Handle(AddBusinessUnitCommand command)
{
businessUnitService.AddCompany(command.name);
}
public void Handle(DeleteBusinessUnitCommand command)
{
invoiceService.DeleteAllInvoicesForCompany(command.ID);
businessUnitService.DeleteCompany(command.ID);
}
}
public class BusinessUnitService : IBusinessUnitService
{
private readonly IUnitOfWork unitOfWork;
private readonly ILogger logger;
public BusinessUnitService(IUnitOfWork unitOfWork,
ILogger logger)
{
this.unitOfWork = unitOfWork;
this.logger = logger;
}
void IBusinessUnitService.AddCompany(string name)
{
// snip... let container call IUnitOfWork.Save()
}
void IBusinessUnitService.DeleteCompany(int ID)
{
// snip... let container call IUnitOfWork.Save()
}
}
public class InvoiceService : IInvoiceService
{
private readonly IUnitOfWork unitOfWork;
private readonly ILogger logger;
public BusinessUnitService(IUnitOfWork unitOfWork,
ILogger logger)
{
this.unitOfWork = unitOfWork;
this.logger = logger;
}
void IInvoiceService.DeleteAllInvoicesForCompany(int ID)
{
// snip... let container call IUnitOfWork.Save()
}
}
con lo anterior mi problema comienza a formar, como yo lo entiendo desde el documentation on ASP .NET PerWebRequest lifetimes, se utiliza el siguiente código:
public T GetInstance()
{
var context = HttpContext.Current;
if (context == null)
{
// No HttpContext: Let's create a transient object.
return this.instanceCreator();
}
object key = this.GetType();
T instance = (T)context.Items[key];
if (instance == null)
{
context.Items[key] = instance = this.instanceCreator();
}
return instance;
}
lo anterior funciona bien para cada solicitud HTTP habrá una válida HttpContext.Current
, sin embargo si spin un nuevo hilo con el ThreadedCommandHandlerProxy
creará e un nuevo hilo y el HttpContext
ya no existirá dentro de ese hilo. Dado que el HttpContext
sería nulo en cada llamada posterior, todas las instancias de objetos inyectadas en los constructores de servicio serían nuevas y únicas, lo contrario al HTTP normal por solicitud web donde los objetos se comparten correctamente como la misma instancia en todos los servicios.
Para resumir lo anterior en preguntas:
¿Cómo hago para conseguir los objetos construidos y elementos comunes inyectados con independencia de que creado a partir de la solicitud HTTP o por medio de un nuevo hilo?
¿Hay alguna consideración especial para tener un UnitOfWork
administrado por un subproceso dentro de un proxy de controlador de comando? ¿Cómo se puede garantizar que se guarde y elimine después de que se haya ejecutado el controlador?
Si tuviéramos un problema con el controlador de comandos/service-layer y no quisiéramos guardar el UnitOfWork
, ¿simplemente lanzaríamos una excepción? En caso afirmativo, ¿es posible detectar esto a nivel global o necesitamos detectar la excepción por solicitud desde un try
- catch
en el decorador o proxy del gestor?
Gracias,
Chris
@Steven gracias, he pasado por esa pregunta, pero no estoy 100% seguro de lo que debería hacer con RegisterPerWebRequest y RegisterLifetimeScope para mi caso de IUnitOfWork y UnitOfWork, en mi caso sería DbContext => UnitOfWorkBase, MyDbContext => UnitOfWork y IObjectContextAdapter => IUnitOfWork? Si pudiera aclarar sería apreciado. También se menciona que hay una serie de formas de hacerlo, ¿es una forma de hacer una extensión explícita de por vida para esto? Sería bueno si no tuviera que derivar de las clases base. –
g18c
Con el nuevo 'async' ActionResults para MVC5, este problema va a ser uno común. – Mrchief
Si la unidad de trabajo es una tarea larga e importante, considere una "cola de trabajos" del bus de mensajes que puede retomar cuando su aplicación regrese de un reciclaje. FYI al leer sobre 'QueueBackgroundWorkItem' Vi que la tarea se matará si tarda más de * 90 segundos * por lo que estás en riesgo si tardas más de un minuto – KCD