Wymagana rejestracja zależy od rodzaju aplikacji jon. Ponieważ mówimy o dwóch różnych frameworkach (MVC i WinForm), obie będą miały inną rejestrację.
W przypadku aplikacji MVC (lub ogólnie aplikacji internetowych) najczęstszą rzeczą do zrobienia jest zarejestrowanie jednostki pracy na per web request basis. Na przykład, po rejestracji będzie buforować jednostkę pracy podczas pojedynczego żądania internetowej:
container.Register<IUnitOfWork>(() =>
{
var items = HttpContext.Current.Items;
var uow = (IUnitOfWork)items["UnitOfWork"];
if (uow == null)
{
items["UnitOfWork"] = uow = container.GetInstance<UnitOfWork>();
}
return uow;
});
Wadą tej rejestracji jest to, że jednostka pracy nie jest umieszczona (w razie potrzeby). Istnieje an extension package dla prostego wtryskiwacza, który dodaje do kontenera metody rozszerzenia RegisterPerWebRequest
, które automatycznie zapewnią, że instancja zostanie usunięta na końcu żądania WWW. Korzystanie z tego pakietu, będzie można wykonać następujące czynności rejestracji:
container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
który jest skrótem do:
container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle());
Windows Forms aplikacji z drugiej strony, jest zazwyczaj pojedynczy gwintowany (pojedynczy użytkownik będzie korzystać z tej aplikacji). Uważam, że nie jest niczym niezwykłym posiadanie pojedynczej jednostki pracy na formę, która jest umieszczona w formularzu zamyka, ale przy użyciu wzorca polecenia/obsługi, myślę, że lepiej jest przyjąć podejście bardziej zorientowane na usługi. Rozumiem przez to, że dobrze byłoby zaprojektować go w taki sposób, aby można było przenieść warstwę biznesową do usługi WCF, bez konieczności wprowadzania zmian w warstwie prezentacji. Możesz to osiągnąć, pozwalając swoim komendom tylko zawierać prymitywy i (inne) DTO s. Więc nie przechowuj encji Entity Framework w swoich komendach, ponieważ znacznie utrudni to serializowanie polecenia, a później doprowadzi do niespodzianek.
Gdy to zrobisz, wygodniej będzie utworzyć nową jednostkę pracy przed uruchomieniem komendy obsługi, ponownie użyć tej samej jednostki pracy podczas wykonywania tego modułu obsługi i zatwierdzić ją, gdy procedura obsługi zakończy się pomyślnie (i zawsze wyrzucaj go). Jest to typowy scenariusz dla Per Lifetime Scope lifestyle. Jest an extension package, który dodaje metody rozszerzenia RegisterLifetimeScope
do kontenera. Korzystanie z tego pakietu, będzie można zrobić po rejestracji:
container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();
który jest skrót do:
container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle());
Rejestracja jest jednak tylko połowa historii. Druga część to decyzja, kiedy zapisać zmiany w jednostce pracy, oraz w przypadku korzystania ze stylu życia w ramach Lifetime Scope, od czego zacząć i zakończyć taki zakres. Ponieważ powinieneś jawnie uruchomić zasięg życia przed wykonaniem polecenia i zakończyć go, gdy polecenie zakończyło wykonywanie, najlepszym sposobem na to jest użycie dekoratora obsługi komend, który może owijać programy obsługi komend. Dlatego w przypadku aplikacji formularzy zwykle rejestruje się dodatkowy dekorator obsługi poleceń, który zarządza zasięgiem życia. Takie podejście nie działa w tym przypadku. Spójrz na poniższy dekoratora, ale należy pamiętać, że jest to błędne :
private class LifetimeScopeCommandHandlerDecorator<T>
: ICommandHandler<T>
{
private readonly Container container;
private readonly ICommandHandler<T> decoratedHandler;
public LifetimeScopeCommandHandlerDecorator(...) { ... }
public void Handle(T command)
{
using (this.container.BeginLifetimeScope())
{
// WRONG!!!
this.decoratedHandler.Handle(command);
}
}
}
Podejście nie działa, ponieważ urządzone obsługi poleceń jest tworzony przed uruchomieniu zakres życia.
Możemy być kuszeni do starają się rozwiązać ten problem w następujący sposób, ale to nie jest poprawna:
using (this.container.BeginLifetimeScope())
{
// EVEN MORE WRONG!!!
var handler = this.container.GetInstance<ICommandHandler<T>>();
handler.Handle(command);
}
Chociaż żądając ICommandHandler<T>
wewnątrz kontekście zakresu twórczości, czy rzeczywiście do wstrzykiwania IUnitOfWork
w tym zakresie, kontener zwróci przewodnik, który jest (ponownie) ozdobiony numerem LifetimeScopeCommandHandlerDecorator<T>
. Wywołanie handler.Handle(command)
spowoduje wywołanie rekursywne, a otrzymamy wyjątek przepełnienia stosu.
Problem polega na tym, że wykres zależności został już zbudowany, zanim będziemy mogli uruchomić zakres życia. Musimy zatem złamać wykres zależności, odraczając budowanie reszty wykresu. Najlepszym sposobem, aby to zrobić, aby zachować czysty design aplikacji] jest zmiana dekoratora na proxy i wstrzyknięcie do niego fabryki, która stworzy typ, który miał zawinąć. Takie LifetimeScopeCommandHandlerProxy<T>
będzie wyglądać następująco:
// This class will be part of the Composition Root of
// the Windows Forms application
private class LifetimeScopeCommandHandlerProxy<T> : ICommandHandler<T>
{
// Since this type is part of the composition root,
// we are allowed to inject the container into it.
private Container container;
private Func<ICommandHandler<T>> factory;
public LifetimeScopeCommandHandlerProxy(Container container,
Func<ICommandHandler<T>> factory)
{
this.factory = factory;
this.container = container;
}
public void Handle(T command)
{
using (this.container.BeginLifetimeScope())
{
var handler = this.factory();
handler.Handle(command);
}
}
}
Wtryskując pełnomocnika, można opóźnić wystąpienie i powstaje w ten sposób, że opóźnienie w konstrukcję (reszta) wykres zależności. Sztuczka polega teraz na zarejestrowaniu tej klasy proxy w taki sposób, aby wstrzyknęła zawinięte instancje, zamiast (oczywiście) wstrzyknąć sobie ponownie. Simple Injector obsługuje wstrzykiwanie Func<T>
fabryk do dekoratorów, więc możesz po prostu użyć RegisterDecorator
, aw tym przypadku nawet metody rozszerzania RegisterSingleDecorator
.
Należy zauważyć, że kolejność, w jakiej dekoratorzy (i ten pełnomocnik) są zarejestrowani (oczywiście) ma znaczenie. Ponieważ ten serwer proxy uruchamia nowy zakres życia, powinien zawinąć dekorator, który zatwierdza jednostkę pracy.Innymi słowy, bardziej kompletna rejestracja będzie wyglądać następująco:
container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();
container.RegisterManyForOpenGeneric(
typeof(ICommandHandler<>),
AppDomain.CurrentDomain.GetAssemblies());
// Register a decorator that handles saving the unit of
// work after a handler has executed successfully.
// This decorator will wrap all command handlers.
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(TransactionCommandHandlerDecorator<>));
// Register the proxy that starts a lifetime scope.
// This proxy will wrap the transaction decorators.
container.RegisterSingleDecorator(
typeof(ICommandHandler<>),
typeof(LifetimeScopeCommandHandlerProxy<>));
Rejestrowanie serwera proxy i dekorator na odwrót oznaczałoby, że TransactionCommandHandlerDecorator<T>
będzie zależeć od innego IUnitOfWork
od reszty wykresu zależności robi, co oznaczałoby, że wszystkie zmiany wprowadzone do jednostki pracy na tym wykresie nie zostaną zatwierdzone. Innymi słowy, twoja aplikacja przestanie działać. Dlatego zawsze dokładnie sprawdzaj tę rejestrację.
Powodzenia.