Od zawsze trochę męczyło mnie to, że jak dodaję nowy interfejs wraz z klasą go implementującą, to muszę pamiętać jeszcze o rejestracji go w kontenerze wstrzykiwania zależności. Aż w pewnym momencie trafiłem na bibliotekę Scrutor i od razu programowanie stało się przyjemniejsze.
Scrutor
Jest to biblioteka, która umożliwia automatyczną rejestrację typów w .Net Core. Udostępnia ona metodę Scan, która przeszukuje podane Assembly w poszukiwaniu określonego interfejsu. Następnie wszystkie typy, które implementują dany interfejs, rejestruje z określoną polityką czasu życia.
Standardowy scenariusz
Załóżmy, że mamy interfejs IUser:
public interface IUser
{
// ...
}
I klasę User, która ten interfejs implementuje:
public class User : IUser
{
// ...
}
Aby zarejestrować te typy w naszym kontenerze wstrzykiwania zależności z czasem życia Transient musielibyśmy zrobić coś takiego:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<IUser, User>();
// ...
}
To samo musielibyśmy zrobić dla wszystkich naszych interfejsów i klas ich implementujących.
Scenariusz z wykorzystaniem biblioteki Scrutor
Najpierw musimy zdefiniować sobie jakiś interfejs, który będzie nam określał z jaką polityką czasu życia należy zarejestrować dany typ. Dla naszych potrzeb nazwijmy go ITransient:
public interface ITransient
{
}
Teraz musimy zmodyfikować interfejs IUser:
public interface IUser : ITransient
{
// ...
}
A następnie zmienić jego rejestrację:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.Scan(scan => scan
.FromAssembliesOf(typeof(Startup))
.AddClasses(classes => classes.AssignableTo(typeof(ITransient)))
.AsImplementedInterfaces()
.WithTransientLifetime());
// ...
}
Co ten kod robi? Na początku wywołujemy metodę Scan i jako argument przekazujemy funkcję. W tej funkcji dla danego Assembly wyszukujemy wszystkie klasy, które implementują interfejs ITransient. Następnie dla każdej z tych klas pobieramy interfejsy, które ona implementuje i rejestrujemy je z czasem życia Transient.
Taki kod piszemy tylko raz i od teraz wszystkie interfejsy, które rozszerzają typ ITransient będą zarejestrowane z typem życia Transient.
Oczywiście, nie musimy tworzyć nowych interfejsów – możemy wykorzystać już istniejące. Załóżmy, że mamy generycznym typ IRepository<>:
public interface IRepository<T> where T : class
{
}
Możemy wykorzystać go do rejestracji naszych wszystkich repozytoriów:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.Scan(scan => scan
.FromAssembliesOf(typeof(Startup))
.AddClasses(classes => classes.AssignableTo(typeof(IRepository<>)))
.AsImplementedInterfaces()
.WithTransientLifetime());
// ...
}
Scrutor radzi sobie również z typami generycznymi.
Rejestracja kolejnych typów
Nie ma ograniczeń co do tego, ile typów rejestrujemy przy użyciu metody Scan. Możemy dodać kolejne rejestracje z tą samą lub inną polityką czasu życia. Załóżmy, że mamy interfejs ISingleton:
public interface ISingleton
{
}
Bez problemy możemy rozszerzyć naszą rejestrację:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.Scan(scan => scan
.FromAssembliesOf(typeof(Startup))
.AddClasses(classes => classes.AssignableTo(typeof(ITransient)))
.AsImplementedInterfaces()
.WithTransientLifetime()
.AddClasses(classes => classes.AssignableTo(typeof(ISingleton)))
.AsImplementedInterfaces()
.WithSingletonLifetime());
// ...
}
Pobieranie Assembl y
W podanych przykładach pierwszą rzecz jaką robimy dla obiektu scan jest wybór Assembly, z którego mają być zarejestrowane typy:
// ...
services.Scan(scan => scan
.FromAssembliesOf(typeof(Startup))
// ...
Ta część może zostać zastąpiona np. przez rejestrację typów z danej aplikacji (używamy do tego statycznej metody GetEntryAssembly z klasy Assembly):
services.Scan(scan =>
{
var assembly = Assembly.GetEntryAssembly();
scan.FromAssemblies(assembly)
.AddClasses(classes => classes.AssignableTo(typeof(ITransient)))
.AsImplementedInterfaces()
.WithTransientLifetime();
});
Albo możemy zdefiniować listę Assembly z których mają być zarejestrowane typy:
services.Scan(scan =>
{
var assemblies = new[] { typeof(CoreProject).Assembly,
typeof(InfrastructureProject).Assembly };
scan.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(ITransient)))
.AsImplementedInterfaces()
.WithTransientLifetime();
});
Podsumowanie
Dzięki bibliotece Scrutor rejestrację robimy raz. Potem nie musimy się już tym przejmować. Jedynie o czym musimy pamiętać to, aby każdy interfejs, który ma być zarejestrowany w naszym kontenerze wstrzykiwania zależności, rozszerzał odpowiedni interfejs. Dla mnie takie podejście jest dużo wygodniejsze.
Oczywiście mogą się trafić typy, które będą specyficzne i trzeba będzie zarejestrować je ręcznie, ale takich typów z reguły jest niedużo.
Pingback: dotnetomaniak.pl
Możliwość komentowania została wyłączona.