Scrutor automatyczna rejestracja typów

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
{
}
view raw IRepository.cs hosted with ❤ by GitHub

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
};
}
}

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

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" });
});
}
view raw Startup.cs hosted with ❤ by GitHub

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" });
});
}
view raw Startup2.cs hosted with ❤ by GitHub

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" });
});
}
view raw Startup3.cs hosted with ❤ by GitHub

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.

Szkolenie modularny monolit w .NET 5

Szkolenie modularny monolit w .NET 5

Zainteresował Ciebie ten temat? A może chcesz więcej? Jak tak to zapraszam na moje autorskie szkolenie o modularnym monolicie w .NET.

2 thoughts on “Scrutor automatyczna rejestracja typów

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.