Moduły Autofac?
Przy pracy z kontenerami Dependency Injection bardzo często dochodzi do sytuacji, gdy kod konfiguracji takiego kontenera bardzo się rozrasta. Dzieje się tak przede wszystkim wtedy, kiedy korzystamy z biblioteki, która nie wspiera autorejestracji (przez dłuższy czas w jednej z aplikacji WPF wykorzystywałem Unity, w którym trzeba było zarejestrować każdy typ ręcznie w kontenerze), przez co klasa konfiguracji może zawierać dziesiątki, jeśli nie setki linijek kodu. Na szczęście Autofac, którego wykorzystuję od dłuższego czasu w swoich projektach, wspiera autorejestrację oraz organizację rejestracji w formie małych klas (moduły Autofac), o których będzie mowa w tym wpisie.
Moduł Autofac jest prostą klasą, w której na ogół nadpisujemy metodę Load zawierającą konfigurację kontenera Autofac. Korzystając z modułów, możemy pogrupować rejestrację typów na mniejsze, logicznie powiązane z sobą elementy, aby późniejsze utrzymanie takich rejestracji było łatwiejsze.
Dodatkowo, gdy tworzymy rozbudowaną aplikację, którą dzielimy na jakieś części w formie modułów lub pluginów, możemy wykorzystać ten mechanizm, aby każdy taki element aplikacji zawierał rejestracje swoich własnych typów w kontenerze.
Jak dodać moduły Autofac do aplikacji?
Na potrzeby dzisiejszego wpisu skorzystam z przykładu pochodzącego z wpisu o użyciu obiektów Lazy do wstrzykiwania zależności, w którym użyłem modułów Autofac do konfiguracji. Myślę, że najlepiej od razu przejść do kodu i zobaczyć, jakie moduły wykorzystałem w przykładzie:
public class DefaultModule : Module | |
{ | |
protected override void Load(ContainerBuilder builder) | |
{ | |
builder.RegisterAssemblyTypes(typeof(DefaultModule).Assembly) | |
.AsImplementedInterfaces() | |
.EnableInterfaceInterceptors() | |
.InterceptedBy(typeof(LoggerInterceptor)); | |
} | |
} |
DefaultModule to podstawowy moduł, który wykorzystuje autorejestrację do zarejestrowania wszystkich klas z aktualnego assembly pod ich interfejsami. Właściwie w każdej swojej aplikacji mam taki moduł, który jest odpowiedzialny za rejestrację większości typów w aplikacji. Dodatkowo w tej rejestracji dodaję jeszcze obsługę interceptora – ale to już temat na oddzielny wpis, który planuję dodać w najbliższym czasie.
public class ControllersModule : Module | |
{ | |
protected override void Load(ContainerBuilder builder) | |
{ | |
builder.RegisterControllers(typeof(AutofacConfig).Assembly); | |
} | |
} |
ControllersModule jest odpowiedzialny za rejestrację kontrolerów ASP.NET MVC w kontenerze.
public class DataContextModule : Module | |
{ | |
protected override void Load(ContainerBuilder builder) | |
{ | |
builder.RegisterType<DataContext>() | |
.InstancePerRequest(); | |
} | |
} |
DataContextModule rejestruje obiekt kontekstowy Entity Framework w kontenerze, używając cyklu życia obiektu jeden per request http.
public class ValidatorsModule : Module | |
{ | |
protected override void Load(ContainerBuilder builder) | |
{ | |
builder.RegisterAssemblyTypes(typeof(ValidatorsModule).Assembly) | |
.Where(t => t.GetInterfaces().Contains(typeof(IValidator))) | |
.InstancePerRequest(); | |
} | |
} |
ValidatorsModule rejestruje walidatory FluentValidation w kontenerze. Ta rejestracja jest potrzebna, ponieważ w aplikacji walidatory wstrzykuje w formie konkretnego typu (często korzystam z różnych walidatorów dla tej samej klasy, w zależności od rodzaju akcji), a nie interfejsu, więc domyślna rejestracja z DefaultModule nie zarejestruje tych typów.
public class LoggerModule : Module | |
{ | |
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger(); | |
protected override void Load(ContainerBuilder builder) | |
{ | |
builder.RegisterInstance(new LoggerInterceptor()); | |
} | |
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, | |
IComponentRegistration registration) | |
{ | |
registration.Activating += OnActivating; | |
base.AttachToComponentRegistration(componentRegistry, registration); | |
} | |
private void OnActivating(object sender, ActivatingEventArgs<object> e) | |
{ | |
_logger.Info($"Activating: {e.Component.Activator.LimitType}"); | |
} | |
} |
LoggerModule jest specjalnym modułem przygotowanym na potrzeby tamtego przykładu. Służy on głównie do logowania informacji o tworzonych obiektach przez kontener, co było przydatne podczas przykładu z obiektami Lazy. Przy okazji pokazuję, że w module nie tylko musimy rejestrować typy w kontenerze, ale możemy zrobić też inne rzeczy.
Mając już przygotowane moduły, należy jeszcze skonfigurować kontener, aby z nich skorzystał. Robi się to bardzo prosto:
public class AutofacConfig | |
{ | |
public static IContainer Configure() | |
{ | |
var builder = new ContainerBuilder(); | |
builder.RegisterAssemblyModules(typeof(AutofacConfig).Assembly); | |
var container = builder.Build(); | |
DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); | |
return container; | |
} | |
} |
Całą pracę wykonuje metoda RegisterAssemblyModules, do której przekazujemy assembly, z którego chcemy załadować moduły. Przykład zbudowany jest z tylko jednego projektu, dlatego wywołują metodę tylko raz.
Podsumowanie
Jak widać, skorzystanie z modułów Autofac powoduje, że kod konfigurujący kontener jest dużo lepiej zorganizowany. Lepiej się go utrzymuje, a znalezienie konkretnej konfiguracji jest łatwiejsze.
Nie zapomnij pobrać przykładu z githuba i sprawdzić, jak działają moduły w praktyce.
1 thought on “Moduły Autofac – jak wykorzystać je do konfiguracji kontenera”