Встроенный механизм внедрения зависимостей .NET позволяет управлять жизненным циклом сервисов. С точки зрения жизненного цикла сервисы могут представлять один из следующих типов:
Transient: при каждом обращении к сервису создается новый объект сервиса
Scoped: создается отдельный контекст или scope, и в рамках этого контекста при всех обращениях у сервису будет использоваться один и тот же объект сервиса.
Singleton: объект сервиса создается при первом обращении к нему, все последующих обращениях используется один и тот же ранее созданный объект сервиса
Для создания каждого типа сервиса предназначен соответствующий метод AddTransient(), AddScoped() и AddSingleton().
Для рассмотрения механизма внедрения зависимостей и жизненного цикла возьмем следующие типы:
public interface ICounter { int Value { get; } } public class RandomCounter : ICounter { static Random rnd = new Random(); private int _value; public RandomCounter() => _value = rnd.Next(0, 1000000); public int Value => _value; }
Интерфейс ICounter представляет сервис, который в свойстве Value хранит некоторое число. Реализацию этого интерфейса - класс RandomCounter использует класс Random для генерации случайного числа в диапазоне от 0 до 1000000 и возвращает это число из свойства Value.
Теперь на примере этих типов рассмотрим уравление жизненным циклом сервисов.
Метод AddTransient()
создает transient-объекты. Такие объекты создаются при каждом обращении к ним. Данный метод имеет ряд перегруженных версий:
AddTransient(Type serviceType)
AddTransient(Type serviceType, Type implementationType)
AddTransient(Type serviceType, Func<IServiceProvider,object> implementationFactory)
AddTransient<TService>()
AddTransient<TService, TImplementation>()
AddTransient<TService>(Func<IServiceProvider,TService> implementationFactory)
AddTransient<TService, TImplementation>(Func<IServiceProvider,TImplementation> implementationFactory)
Используем данный метод для добавления сервисов:
using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection() .AddTransient<ICounter, RandomCounter>(); using ServiceProvider serviceProvider = services.BuildServiceProvider(); PrintCounters(); PrintCounters(); void PrintCounters() { var counter1 = serviceProvider.GetService<ICounter>(); var counter2 = serviceProvider.GetService<ICounter>(); Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}"); } public interface ICounter { int Value { get; } } public class RandomCounter : ICounter { static Random rnd = new Random(); private int _value; public RandomCounter() => _value = rnd.Next(0, 1000000); public int Value => _value; }
Для большей показательности я вынес логику по получению сервиса в отдельный метод PrintCounters. Причем в каждом методе два раза получаем сервис ICounter и выводим на консоль значение его свойства Value. Консольный вывод программы в моем случае:
Counter1: 939614; Counter2: 862579 Counter1: 741773; Counter2: 730321
Как видно, в программе идет 4 обращения к провайдеру сервисов для получения сервиса ICounter. И при каждом обращении создается свой объект RandomCounter. Соответственно во всех четырех случаях случайные числа будут разные.
Для сервисов, которые добавляются с помощью метода AddSingleton()
создается один объект для всех последующих обращений. Этот метод имеет все те же
перегруженые версии, что и AddTransient. Для применения AddSingleton изменим код приложения:
using Microsoft.Extensions.DependencyInjection; IServiceCollection services = new ServiceCollection() .AddSingleton<ICounter, RandomCounter>(); using ServiceProvider serviceProvider = services.BuildServiceProvider(); PrintCounters(); PrintCounters(); void PrintCounters() { var counter1 = serviceProvider.GetService<ICounter>(); var counter2 = serviceProvider.GetService<ICounter>(); Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}"); } public interface ICounter { int Value { get; } } public class RandomCounter : ICounter { static Random rnd = new Random(); private int _value; public RandomCounter() => _value = rnd.Next(0, 1000000); public int Value => _value; }
Здесь та же самая логика, только теперь сервис ICounter определен как синглтон, соответственно при каждом обращении к провайдеру сервисов получим один и тот же объект ICounter:
Counter1: 650427; Counter2: 650427 Counter1: 650427; Counter2: 650427
Для сервисов, которые добавляются методом AddScoped, будет создаваться один экземпляр объекта для одного контекста или scope. Здесь под контекстом понимает область видимости объекта IServiceScope. Для получения этого объекта у ServiceProvider вызывается метод CreateScope():
using ServiceProvider serviceProvider = services.BuildServiceProvider(); using (IServiceScope scope = serviceProvider.CreateScope()) { // контекст IServiceScope }
Например, изменим добавление сервиса, применив метод AddScoped()
:
using Microsoft.Extensions.DependencyInjection; IServiceCollection services = new ServiceCollection() .AddScoped<ICounter, RandomCounter>(); using ServiceProvider serviceProvider = services.BuildServiceProvider(); PrintCounters(); PrintCounters(); void PrintCounters() { using IServiceScope scope = serviceProvider.CreateScope(); // контекст scope var counter1 = scope.ServiceProvider.GetService<ICounter>(); var counter2 = scope.ServiceProvider.GetService<ICounter>(); Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}"); } public interface ICounter { int Value { get; } } public class RandomCounter : ICounter { static Random rnd = new Random(); private int _value; public RandomCounter() => _value = rnd.Next(0, 1000000); public int Value => _value; }
Здесь контекст одного объекта IServiceScope ограничивается методом PrintCounters(). Для получения сервиса в пределах этого контекста сначлаа получаем у scope провайдер сервиса и через него получаем сервис ICounter:
var counter1 = scope.ServiceProvider.GetService<ICounter>();
Поскольку здесь метод PrintCounters вызывается два раза, соответственно будут создаваться два разных контекста. И в рамках каждого контекста мы будем получать свой объект ICounter:
Counter1: 620832; Counter2: 620832 Counter1: 816555; Counter2: 816555