Wprowadzenie
Od wielu lat wykorzystuję kontener Autofac do wstrzykiwania zależności. Najbardziej przypadła mi do gustu możliwość automatycznej rejestracji typów, dzięki czemu jedną linijką w kodzie można zarejestrować i skonfigurować całą rodzinę typów (implementującą na przykład jakiś interfejs). Gdy zaczynałem pracę z .NET Core, automatyczna rejestracja typów była jedną z rzeczy, której brakowało mi w domyślnym kontenerze. Na szczęście biblioteka Scrutor uzupełnia ten brak. Zobacz, jak dodać automatyczną rejestrację do wbudowanego kontenera w .NET Core / .NET 5.
Automatyczna rejestracja
Automatyczna rejestracja zwana również skanowaniem pakietów (assembly scanning) jest mechanizmem, który ułatwia nam rejestrację typów. Bez niej musielibyśmy każdy typ ręcznie rejestrować w kontenerze. Podczas samego programowania może nie jest to jakoś mocno problematyczne, bo wystarczy dodać tylko jedną linijkę kodu dla każdego typu, ale z czasem powstają nam małe potworki, w których mamy kilkadziesiąt lub nawet kilkaset linii kodu odpowiedzialnych za rejestrację typów.
Przez to z czasem jest dość trudno utrzymać ten fragment kodu. Szczególnie gdy potrzebujemy zmienić konfigurację jakiejś grupy typów (np. zmienić jej cykl życia). Wtedy bardzo łatwo o przegapienie jakiejś pojedynczej rejestracji i doprowadzenie do sytuacji, w której jakiś pojedynczy typ ma inne zachowanie niż pozostałe, co później może być ciężkie do wyłapania.
Bardzo często w aplikacji grupujemy typy w „rodziny” poprzez implementacje jakiegoś bazowego interfejsu. Często nawet ten interfejs sam nic nie zawiera i służy tylko do oznaczenia nim właśnie grupy typów – tak zwany marker interfejs. Można go wykorzystać w automatycznej rejestracji, czy w ograniczeniach w typach generycznych.
Przykładem takiej grupy obiektów mogą być repozytoria, które bardzo często wykorzystujemy w warstwie dostępu do danych. Mamy wtedy bazowy interfejs IRepository, który w niektórych implementacjach może być pusty:
public interface IRepository | |
{ | |
} |
Istnieje wiele różnych podejść do implementacji wzorca repozytorium. W tym wpisie chcę bazować na tym wzorcu, bo zapewne go znasz i wykorzystujesz. Sama implementacja będzie bardzo prosta i służy jedynie pokazaniu automatycznej rejestracji typów z użyciem biblioteki Scrutor.
Implementacja specyficznych interfejsów i samych klas w przykładzie wygląda tak:
public interface ICategoryRepository : IRepository | |
{ | |
Category GetById(Guid id); | |
} | |
public class CategoryRepository : ICategoryRepository | |
{ | |
public Category GetById(Guid id) | |
{ | |
return new Category() | |
{ | |
Id = id | |
}; | |
} | |
} | |
public interface IProductRepository : IRepository | |
{ | |
Product GetById(Guid id); | |
} | |
public class ProductRepository : IProductRepository | |
{ | |
public Product GetById(Guid id) | |
{ | |
return new Product() | |
{ | |
Id = id | |
}; | |
} | |
} |
Scrutor automatyczna rejestracja
W domyślnym kontenerze w .NET Core / .NET 5 musielibyśmy każde z repozytoriów zarejestrować niezależnie. W klasie Startup mielibyśmy coś takiego:
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddScoped<IProductRepository, ProductRepository>(); | |
services.AddScoped<ICategoryRepository, CategoryRepository>(); | |
services.AddControllers(); | |
services.AddSwaggerGen(c => | |
{ | |
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ScrutorTests", Version = "v1" }); | |
}); | |
} |
Prawdopodobnie te dwie linijki byłyby opakowane w jakąś metodę, aby metoda ConfigureServices nie była zbyt mocno rozbudowana.
Po zainstalowaniu biblioteki Scrutor (https://github.com/khellang/Scrutor) dany fragment kodu możemy zapisać w trochę inny sposób:
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.Scan(s => s.FromAssemblyOf<IRepository>() | |
.AddClasses(c => c.AssignableTo<IRepository>()) | |
.AsImplementedInterfaces() | |
.WithScopedLifetime()); | |
services.AddControllers(); | |
services.AddSwaggerGen(c => | |
{ | |
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ScrutorTests", Version = "v1" }); | |
}); | |
} |
Wykorzystujemy nową metodę Scan, która umożliwia przeskanowanie assembly i automatyczną rejestrację typów. W przykładzie określam, że chcę przeskanować assembly, w którym znajduje się typ IRepository. Następnie za pomocą metody AddClasses rejestrujemy klasy, implementujące interfejs IRepository. Na końcu rejestrujemy typy pod interfejsami, jakie implementują i ustawiamy ich cykl życia jako scoped.
Powyższy kod zarejestruje każdy typ w identyczny sposób, co na dłuższą metę jest dużo lepszym rozwiązaniem niż ręczna rejestracja każdego typu.
Rejestrację warto opakować sobie na przykład w extensions method, aby zawartość metody ConfigureServices była zwięzła i zwarta, a nawet z czasem można pokusić się o wydzielenie tego do jakiejś zewnętrznej biblioteki i używanie tego w wielu projektach.
Taka metoda może wyglądać tak:
public static class ServiceCollectionExtensions | |
{ | |
public static IServiceCollection AddRepositories(this IServiceCollection services) | |
{ | |
services.Scan(s => s.FromAssemblyOf<IRepository>() | |
.AddClasses(c => c.AssignableTo<IRepository>()) | |
.AsImplementedInterfaces() | |
.WithScopedLifetime()); | |
return services; | |
} | |
} |
A jej użycie tak:
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddRepositories(); | |
services.AddControllers(); | |
services.AddSwaggerGen(c => | |
{ | |
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ScrutorTests", Version = "v1" }); | |
}); | |
} |
Prawda, że przyjemniej?
Przykład
Na githubie https://github.com/danielplawgo/ScrutorTests znajduje się przykład do tego wpisu. Po jego pobraniu nie jest potrzebna jakaś dodatkowa konfiguracja. Można go uruchomić i się bawić.
Podsumowanie
Automatyczna rejestracja jest fajną funkcjonalnością, potrafi zaoszczędzić trochę czasu, kod rejestracji jest lepiej zorganizowany, a do tego nie będziemy mieli różnej konfiguracji rejestracji typów z tej samej rodziny.
Wcześniej dorzucałem zawsze Autofaca do projektu, głównie aby mieć autorejestrację. Ostatnio korzystam ze Scrutora, szczególnie gdy nie potrzebuję jakichś bardziej zaawansowanych opcji, które daje Autofac.
2 thoughts on “Scrutor automatyczna rejestracja typów”