Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Все объекты, которые используются в ASP.NET Core, имеет три варианта жизненного цикла. Singleton-объекты создаются один раз при запуске приложения,
и при всех запросах к приложению оно использует один и тот же singleton-объект. К подобным singleton-объектам относятся, к примеру, компоненты
middleware или сервисы, которые регистрируются с помощью метода AddSingleton()
.
Transient-объекты создаются каждый раз, когда нам требуется экземпляр определенного класса. А scoped-объекты создаются по одному на каждый запрос.
Одни объекты или сервисы с помощью встроенного механизма dependency injection можно передать в другие объекты. Наиболее распространенный способ внедрения объектов предсталяет инъекция через конструктор. Однако начиная с версии ASP.NET Core 2.0 мы не можем передавать scoped-сервисы в конструктор singleton-объектов.
Например, пусть будут опеделены следующие классы:
public interface ITimer { string Time { get; } } public class Timer : ITimer { public Timer() { Time = System.DateTime.Now.ToString("hh:mm:ss"); } public string Time { get; } } public class TimeService { private ITimer _timer; public TimeService(ITimer timer) { _timer = timer; } public string GetTime() { return _timer.Time; } }
TimeService получает через конструктор сервис ITimer и использует его для получения текущего времени.
Также пусть будет определен компонент middleware TimerMiddleware:
public class TimerMiddleware { private readonly RequestDelegate _next; TimeService _timeService; public TimerMiddleware(RequestDelegate next, TimeService timeService) { _next = next; _timeService = timeService; } public async Task Invoke(HttpContext context) { context.Response.ContentType = "text/html; charset=utf-8"; await context.Response.WriteAsync($"Текущее время: {_timeService?.GetTime()}"); } }
Компонент TimerMiddleware получает сервис TimeService и отправляет в ответ клиенту информацию о текущем времени.
TimerMiddleware является singleton-объектом. И теперь в классе Startup зарегистрируем сервис TimeService как scoped-объект:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITimer, Timer>(); services.AddScoped<TimeService>(); } public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage(); app.UseMiddleware<TimerMiddleware>(); } }
Если мы запустим приложение, то мы столкнемся с ошибкой типа "InvalidOperationException: Cannot resolve scoped service 'DIApp.TimeService' from root provider.":
То есть на момент создания объекта TimerMiddleware scoped-сервис TimeService еще не установлен, соответственно он использоваться не может. А без создания объекта TimeService нельзя создать объект TimerMiddleware.
Аналогичная ситуация может возникнуть, если TimeService добавляется как Transient, а сервис ITimer определен как Scoped:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<ITimer, Timer>(); services.AddTransient<TimeService>(); }
В этом случае для создания объекта TimeService надо получить сервис ITimer, но на момент вызова конструктора TimerMiddleware сервис ITimer еще неопределен:
Для выхода из этой ситуации ни TimeService, ни ITimer не должны иметь жизненный цикл Scoped. То есть это может быть Transient или Singleton.
Рассмотрим еще одну ситуацию, с которой можно столкнуться в любой части приложения, а не только в конструкторе middleware, когда сервис TimeService представляет singleton, а ITimer - scoped-объект:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<ITimer, Timer>(); services.AddSingleton<TimeService>(); }
И, допустим, эти сервисы используются в TimerMiddleware непосредственно при обработке запроса в методе Invoke/InvokeAsync:
public class TimerMiddleware { private readonly RequestDelegate _next; public TimerMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, TimeService timeService) { context.Response.ContentType = "text/html; charset=utf-8"; await context.Response.WriteAsync($"Текущее время: {timeService?.GetTime()}"); } }
При запуске приложения мы опять же столкнемся с ошибкой, только немного другой "Cannot consume scoped service 'DIApp.ITimer' from singleton 'DIApp.TimeService'"
Но суть будет та же самая - мы не можем по умолчанию передавать в конструктор singleton-объекта scoped-сервис.