Жизненный цикл зависимостей

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 07.11.2019

Используя различные методы внедрения зависимостей, можно управлять жизненным циклом создаваемых сервисов. Сервисы, которые создаются механизмом Depedency Injection, могут представлять один из следующих типов:

  • Transient: при каждом обращении к сервису создается новый объект сервиса. В течение одного запроса может быть несколько обращений к сервису, соответственно при каждом обращении будет создаваться новый объект. Подобная модель жизненного цикла наиболее подходит для легковесных сервисов, которые не хранят данных о состоянии

  • Scoped: для каждого запроса создается свой объект сервиса. То есть если в течение одного запроса есть несколько обращений к одному сервису, то при всех этих обращениях будет использоваться один и тот же объект сервиса.

  • Singleton: объект сервиса создается при первом обращении к нему, все последующие запросы используют один и тот же ранее созданный объект сервиса

Для создания каждого типа сервиса предназначен соответствующий метод AddTransient(), AddScoped() и AddSingleton().

Для рассмотрения механизма внедрения зависимостей и жизненного цикла возьмем проект ASP.NET Core по типу Empty. Вначале определим простейший сервис. Для этого добавим в проект папку, которую назовем Services. И в нее затем добавим интерфейс ICounter:

public interface ICounter
{
	int Value { get; }
}

И добавим в эту же папку Services класс RandomCounter, который будет реализовать данный интерфейс:

using System;

public class RandomCounter : ICounter
{
	static Random Rnd = new Random();
	private int _value;
	public RandomCounter()
	{
		_value = Rnd.Next(0, 1000000);
	}
	public int Value
	{
		get { return _value; }
	}
}

Суть данного класса будет состоять в генерации некоторого случайного числа в диапазоне от 0 до 1000000.

И также в папку Services добавим новый класс, который нам более детально поможет разобраться в механизме Depedency Injection. Этот класс назовем CounterService, и он будет содержать следующий код:

public class CounterService
{
	protected internal ICounter Counter { get; }
	public CounterService(ICounter counter)
	{
		Counter = counter;
	}
}

Данный класс просто устанавливает объект ICounter, передаваемый через конструктор.

Для работы с сервисами определим компонент middleware, который назовем CounterMiddleware:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using DIApp.Services;

namespace DIApp
{
    public class CounterMiddleware
    {
        private readonly RequestDelegate _next;
        private int i = 0; // счетчик запросов
        public CounterMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext httpContext, ICounter counter, CounterService counterService)
        {
            i++;
            httpContext.Response.ContentType = "text/html;charset=utf-8";
            await httpContext.Response.WriteAsync($"Запрос {i}; Counter: {counter.Value}; Service: {counterService.Counter.Value}");
        }
    }
}

Для получения зависимостей здесь используется метод InvokeAsync, в котором передаются две зависимости ICounter и CounterService. В самом методе выводятся значения Value из обоих зависимостей. Причем сервис CounterService сам использует зависимость ICounter.

То есть структура проекта будет выглядеть следующим образом:

Depedency Injection in ASP.NET Core

Для тестирования сервисов изменим код класса Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using DIApp.Services;

namespace DIApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ICounter, RandomCounter>();
            services.AddTransient<CounterService>();
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<CounterMiddleware>();
        }
    }
}

В методе Configure CounterMiddleware встраивается в конвейер обработки запроса.

Для того, чтобы передать нужные зависимости, необходимо вызвать методе ConfigureServices() один из методов, который определяет жизненный цикл зависимостей.

AddTransient

Метод 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)

Для использования в методе ConfigureServices() пусть будут определены следующие вызовы:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ICounter, RandomCounter>();
	services.AddTransient<CounterService>();
}

Запустим проект:

AddTransient в ASP.NET Core DI

В нашем случае CounterMiddleware получает объект ICounter, для которого создается один экземпляр класса RandomCounter. CounterMiddleware также получает объект CounterService, который также использует ICounter. И для этого ICounter будет создаваться второй экземпляр класса RandomCounter. Поэтому генерируемые случайные числа обоими экземплярами не совпадают. Таким образом, применение AddTransient создаст два разных объекта RandomCounter.

При втором и последующих запросах к контроллеру будут создаваться новые объекты RandomCounter.

AddScoped

Метод AddScoped создает один экземпляр объекта для всего запроса. Он имеет те же перегруженные версии, что и AddTransient. Для его применения изменим метод ConfigureServices() в классе Startup (весь остальной код остается прежним):

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ICounter, RandomCounter>();
    services.AddScoped<CounterService>();
}
AddScoped в ASP.NET Core DI

Теперь в рамках одного и того же запроса и CounterMiddleware и сервис CounterService будут использовать один и тот же объект RandomCounter. При следующем запросе к приложению будет генерироваться новый объект RandomCounter.

AddSingleton

AddSingleton создает один объект для всех последующих запросов, при этом объект создается только тогда, когда он непосредственно необходим. Этот метод имеет все те же перегруженые версии, что и AddTransient и AddScoped.

Для применения AddSingleton изменим метод ConfigureServices():

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ICounter, RandomCounter>();
    services.AddSingleton<CounterService>();
}

Как можно заметить, все три метода внедрения зависимостей имеют один и тот же принцип использования, различается только название метода. И в этом случае при нескольких последовательных запросах мы получим следующий результат:

AddSingleton в ASP.NET Core DI

Для создания singleton-объектов необязательно полагаться на механизм DI. Мы его можем сами создать и передать в нужный метод:

public void ConfigureServices(IServiceCollection services)
{
    RandomCounter rndCounter = new RandomCounter();
    services.AddSingleton<ICounter>(rndCounter);
    services.AddSingleton<CounterService>(new CounterService(rndCounter));
}

Работа приложения в данном случае будет аналогична предыдущему примеру.

Использование фабрики сервисов

Ряд перегруженных версий у всех трех методов в качестве параметра могут принимать фабрику сервисов - Func<IServiceProvider,object> implementationFactory, которая управляет созданием объектов сервисов. Фабрика позволяет нам применить более сложную логику по созданию сервиса, например, добавить условия создания:

public void ConfigureServices(IServiceCollection services)
{
	services.AddTransient<RandomCounter>();
	services.AddTransient<ICounter>(provider => {

		// получаем выше зарегистрированный сервис RandomCounter
        var counter = provider.GetService<RandomCounter>();
        return counter;
	});
	services.AddTransient<CounterService>();
}

Или к примеру у нас есть зависимость IMessageSender и есть две ее реализации - EmailMessageSender и SmsMessageSender:

services.AddTransient<IMessageSender>(provider=> {

    if (DateTime.Now.Hour >= 12) return new EmailMessageSender();
    return new SmsMessageSender();
});
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850