Sto usando Simple Injector per gestire la durata delle mie dipendenze iniettate (in questo caso UnitOfWork
), e sono molto contento di avere un decoratore separato piuttosto che il mio il gestore di servizi o comandi che si occupa del salvataggio e dello smaltimento rende molto più semplice il codice durante la scrittura di livelli di logica aziendale (seguo l'architettura descritta in this blog post).Come configurare Simple Injector per eseguire thread in background in ASP.NET MVC
Quanto sopra funziona perfettamente (e molto facilmente) utilizzando il pacchetto Simple Injector MVC NuGet e il seguente codice durante la costruzione del contenitore radice della composizione, se nel grafico esiste più di una dipendenza, la stessa istanza viene iniettata attraverso tutto - perfetto per il contesto del modello Entity Framework.
private static void InitializeContainer(Container container)
{
container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
// register all other interfaces with:
// container.Register<Interface, Implementation>();
}
ora ho bisogno di eseguire alcuni thread in background e capire da semplice iniettore documentation on threads che i comandi possono essere proxied come segue:
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);
});
}
}
Tuttavia, da questa filettatura esempio di documentazione Posso vedere le fabbriche che sono usate, se introduco le fabbriche ai miei comandi e al livello di servizio le cose si confonderanno e saranno inconcludenti sistente come avrò diverse metodologie di risparmio per servizi diversi (un contenitore gestisce il risparmio, altre fabbriche istanziati all'interno maniglia servizi salva e lo smaltimento) - si può vedere come semplice e chiaro il codice di servizio scheletro è senza fabbriche:
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 quanto sopra il mio problema comincia a formare, come ho capito dal documentation on ASP .NET PerWebRequest lifetimes, il seguente codice viene utilizzato:
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;
}
è possibile che questo funziona bene per ogni richiesta HTTP ci sarà una valida HttpContext.Current
, se mi spin una nuova discussione con lo ThreadedCommandHandlerProxy
creato e un nuovo thread e lo HttpContext
non esisterà più all'interno di quel thread.
Poiché il HttpContext
sarebbe nullo in ogni chiamata successiva, tutte le istanze degli oggetti immessi nei costruttori di servizio sarebbero nuovi e unici, l'opposto del normale HTTP per richiesta Web in cui gli oggetti sono condivisi correttamente come la stessa istanza in tutti i servizi.
Quindi, per riassumere quanto sopra in domande:
Come potrei fare per ottenere gli oggetti costruiti e oggetti comuni iniettato indipendentemente dal fatto creata dalla richiesta HTTP o tramite un nuovo thread?
Esistono considerazioni speciali per avere un UnitOfWork
gestito da un thread all'interno di un proxy gestore comandi? Come si può garantire che venga salvato e smaltito dopo l'esecuzione del gestore?
Se avessimo un problema all'interno del gestore di comandi/livello di servizio e non volessimo salvare lo UnitOfWork
, dovremmo semplicemente lanciare un'eccezione? In tal caso, è possibile rilevarlo a livello globale o è necessario rilevare l'eccezione per richiesta all'interno di uno try
- catch
nel decoratore o nel proxy del gestore?
Grazie,
Chris
@Steven grazie, sono passati attraverso questa domanda però io non sono sicuro al 100% su quello che devo fare con la RegisterPerWebRequest e RegisterLifetimeScope per il mio caso di IUnitOfWork e UnitOfWork, in il mio caso sarebbe DbContext => UnitOfWorkBase, MyDbContext => UnitOfWork e IObjectContextAdapter => IUnitOfWork? Se potessi chiarire, sarebbe apprezzato. Anche il suo citato ci sono un certo numero di modi per farlo, è un modo per fare un'estensione di durata esplicita per questo? Sarebbe bello se non dovesse derivare dalle classi base. –
g18c
Con i nuovi ActionResults 'async' per MVC5, questo problema sarà comune. – Mrchief
Se l'unità di lavoro è un'attività di lunga durata e importante, considera una "coda di lavoro" del bus dei messaggi che puoi raccogliere quando l'app torna da un riciclo. A proposito, leggendo su 'QueueBackgroundWorkItem' ho visto che l'attività sarà terminata se ci vorrà più di * 90 secondi *, quindi rischierai se impieghi più di un minuto – KCD