Autofac ? różne sposoby rejestracji usług
Gdy dobrych kilka lat temu zaczynałem programować w C#, nie miałem pojęcia o czymś takim jak kontener IoC. Każdy obiekt tworzyłem klasycznie — poprzez zastosowanie słowa kluczowego new: StringBuilder sb = new StringBuilder(); Z takiego sposobu korzystałem również w przypadku własnych klas, niezależnie od tego, czy implementowały one jakieś interfejsy, czy też nie. Sytuacja zmieniła się diametralnie, gdy zacząłem programować w ASP.NET MVC. Tam kontener IoC jest naturalnym elementem i byłoby bardzo trudno korzystać z tej technologii bez stosowania Dependency Injection. Wtedy zakochałem się w tym rozwiązaniu, które po wstępnym zapoznaniu okazało się niezwykle proste, intuicyjne i otworzyło ogrom nowych możliwości. Zalet ma naprawdę sporo, ale dla mnie dwie największe to:
- Możliwe jest sterowanie tworzeniem obiektów o wskazanych typach w jednym miejscu.
- Stosowanie IoC zwiększa testowalność pisanego przez nas kodu.
Oczywiście wzorzec Dependency Injection sam w sobie ma dużo więcej zalet. Ponadto jego możliwości może znacząco zwiększyć odpowiedni kontener IoC — np. tytułowy Autofac, który, odpowiednio skonfigurowany, pozwala nie tylko na zarządzanie procesem tworzenia obiektów, ale również ich niszczenie. W dzisiejszym wpisie przedstawiam zasadniczą różnicę pomiędzy poszczególnymi sposobami rejestracji usług w Autofacu. Przygotowanie Zakładam, że czytelnik zna ideę IoC i przynajmniej pobieżnie wie, jak korzystać z biblioteki Autofac. Bazową bibliotekę Autofaca można pobrać stąd. W tym miejscu można natomiast przeczytać artykuł typu „getting started”. W przypadku konkretnych rozwiązań (ASP.NET MVC, UWP itp.) warto skorzystać z dodatkowych paczek integracyjnych, które bez problemu można znaleźć na NuGet. Wprowadzenie teoretyczne Autofac oferuje kilka różnych sposobów rejestracji usług, które w praktyce mają wpływ na ich późniejsze wyciąganie z kontenera. W większości przypadków wystarczą trzy poniższe rodzaje:
- InstancePerDependency — domyślny sposób rejestracji, który można również określić wprost właśnie za pomocą wywołania wskazanej metody. W takim trybie Autofac zawsze zwraca nową instancję usługi przy każdym wywołaniu metody Resolve. Niezależnie, czy jesteśmy w obszarze scope (o tym za chwilę), czy też nie. Obiekt takiego typu zostanie zneutralizowany w standardowy sposób.
- InstancePerLifetimeScope — w obszarze jednego scope zwracana jest zawsze ta sama instancja określonego obiektu. Czyli jeśli np. korzystamy z serwisu typu DatabaseManager w controlerze w ASP.NET MVC, to przy każdorazowym wywołaniu metody Resolve, wewnątrz tego zakresu, zawsze dostaniemy instancję tego samego obiektu. W sytuacji gdy nasze serwisy implementują interfejs IDisposable, to na takowych obiektach utworzonych wewnątrz określonego scope zostanie wywołana metoda Dispose po zamknięciu tegoż zakresu (http://docs.autofac.org/en/latest/lifetime/disposal.html).
- SingleInstance — to takie nowoczesne, bardziej eleganckie podejście do tworzenia singletonów. Obiekt typu SingleInstance żyje przez cały czas życia kontenera Autofac.
Przykład praktyczny Jako przykład praktyczny wykorzystam delikatnie zmodyfikowany kod z oficjalnej strony Autofaca. Poniżej kod dla testów mechanizmu „per dependency”. Pozostałe testy będą się różnić inną treścią metody RegisterServices. [sourcecode language="csharp"] public interface IOutput { void Write(string content); } public class ConsoleOutput : IOutput { public void Write(string content) { Console.WriteLine(content); } } public interface IDateWriter { void WriteData(); } public class TodayWriter : IDateWriter { private readonly IOutput _output; private int counter = 0; public TodayWriter(IOutput output) { this._output = output; } public void WriteData() { ++counter; this._output.Write(DateTime.Today.ToShortDateString()); this._output.Write($"{this.GetHashCode()}_{counter}"); } } public static class Program { private static IContainer Container { get; set; } static void Main(string[] args) { var builder = new ContainerBuilder(); RegisterServices(builder); Container = builder.Build(); WriteData(); Console.ReadKey(); } public static void WriteData() { using (var scope = Container.BeginLifetimeScope()) { var writer = scope.Resolve<IDateWriter>(); writer.WriteData(); var anotherWriter = scope.Resolve<IDateWriter>(); anotherWriter.WriteData(); } using (var scope = Container.BeginLifetimeScope()) { var writer = scope.Resolve<IDateWriter>(); writer.WriteData(); var anotherWriter = scope.Resolve<IDateWriter>(); anotherWriter.WriteData(); } } private static void RegisterServices(ContainerBuilder builder) { builder.RegisterType<ConsoleOutput>().As<IOutput>(); builder.RegisterType<TodayWriter>().As<IDateWriter>(); } } [/sourcecode] Sercem przykładu jest metoda WriteData, wewnątrz której tworzymy dwa niezależne obiekty scope. Wewnątrz każdego bloku wykonujemy dokładnie to samo — pobieramy dwa razy obiekt typu IDateWriter, na którym wywołujemy zaimplementowaną metodę WriteData. Każde wywołanie metody WriteData wyświetla aktualną datę oraz hash aktualnego obiektu writera i licznik. W przypadku podejścia „per dependency” zawsze będzie to inny hash, a licznik będzie zawsze wynosił 1. 17.10.2016 31609076_1 17.10.2016 20903718_1 17.10.2016 51746094_1 17.10.2016 41215084_1 Zmieńmy teraz metodę RegisterServices w taki sposób, by wykorzystywała podejście InstancePerLifetimeScope: [sourcecode language="csharp"] private static void RegisterServices(ContainerBuilder builder) { builder.RegisterType<ConsoleOutput().As<IOutput>().InstancePerLifetimeScope(); builder.RegisterType<TodayWriter>().As<IDateWriter().InstancePerLifetimeScope(); } [/sourcecode] Poniżej rezultat działania: 17.10.2016 39598739_1 17.10.2016 39598739_2 17.10.2016 55867199_1 17.10.2016 55867199_2 Zaszła tutaj dość istotna różnica. W każdym bloku using, przy każdym żądaniu zwracany jest ten sam obiekt. Widać to po tym samym hashu, a także po inkrementacji licznika. Ostatni przykład dotyczy oczywiście podejścia SingleInstance: [sourcecode language="csharp"] private static void RegisterServices(ContainerBuilder builder) { builder.RegisterType<ConsoleOutput>().As<IOutput>().SingleInstance(); builder.RegisterType<odayWriter>().As<IDateWriter>().SingleInstance(); } [/sourcecode] Poniżej rezultat po zmianie: 17.10.2016 39598739_1 17.10.2016 39598739_2 17.10.2016 39598739_3 17.10.2016 39598739_4 Tak jak zapewne się spodziewaliście, każde wywołanie metody Resolve, zwróciło ten sam obiekt, co widać po stałym hashu, a także wzrastającej wartości licznika. Autofac działa idealnie :) Odpowiednia konfiguracja jest więc kluczem do sukcesu.
Zobacz nasze propozycje
-
- Druk
- PDF + ePub + Mobi
(38,35 zł najniższa cena z 30 dni)
35.40 zł
59.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(32,43 zł najniższa cena z 30 dni)
29.94 zł
49.90 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(64,35 zł najniższa cena z 30 dni)
59.40 zł
99.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(41,40 zł najniższa cena z 30 dni)
41.40 zł
69.00 zł (-40%) -
- Druk
Niedostępna
-
- Druk
- PDF + ePub + Mobi
(38,35 zł najniższa cena z 30 dni)
35.40 zł
59.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(40,20 zł najniższa cena z 30 dni)
40.20 zł
67.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(29,40 zł najniższa cena z 30 dni)
29.40 zł
49.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(29,94 zł najniższa cena z 30 dni)
29.94 zł
49.90 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(40,20 zł najniższa cena z 30 dni)
40.20 zł
67.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(71,40 zł najniższa cena z 30 dni)
71.40 zł
119.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(41,40 zł najniższa cena z 30 dni)
41.40 zł
69.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(83,85 zł najniższa cena z 30 dni)
77.40 zł
129.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(44,85 zł najniższa cena z 30 dni)
41.40 zł
69.00 zł (-40%) -
- Druk
(107,40 zł najniższa cena z 30 dni)
107.40 zł
179.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(47,40 zł najniższa cena z 30 dni)
47.40 zł
79.00 zł (-40%) -
- ePub + Mobi
- Audiobook MP3
(31,12 zł najniższa cena z 30 dni)
29.90 zł
39.90 zł (-25%) -
- Druk
- PDF + ePub + Mobi
(99,50 zł najniższa cena z 30 dni)
119.40 zł
199.00 zł (-40%) -
- Druk
- PDF + ePub + Mobi
(59,40 zł najniższa cena z 30 dni)
59.40 zł
99.00 zł (-40%) -
- Videokurs
(119,40 zł najniższa cena z 30 dni)
39.90 zł
199.00 zł (-80%)