Применение сервисов в middleware

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

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

После добавления сервисов в классе Startup они становятся доступны приложению, в том числе и в кастомных компонентах middleware. В middleware мы можем получить зависимости тремя способами:

  • Через конструктор

  • Через параметр метода Invoke/InvokeAsync

  • Через свойство HttpContext.RequestServices

При этом надо учитывать, что компоненты middleware создаются при создании класса Startup и живут в течение всего жизненного цикла приложения. То есть при последующих запросах asp.net core использует уже ранее созданный компонент. И это налагает ограничения на использование зависимостей в middleware.

В частности, если конструктор передается transient-сервис, который создается при каждом обращении к нему, то при последующих запросах мы будем использовать тот же самый сервис, так как конструктор middleware вызывается один раз - при создании приложения.

Например, определим сервис TimeService:

public class TimeService
{
	public TimeService()
	{
		Time = System.DateTime.Now.ToString("hh:mm:ss");
	}
	public string Time { get; }
}

В конструкторе устанавливается свойство, которое хранит текущее время в виде строки.

Добавим новый компонент TimerMiddleware, который будет использовать этот сервис для вывода времени на веб-страницу:

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

namespace DIApp
{
    public class TimerMiddleware
    {
        private readonly RequestDelegate _next;
        TimeService _timeService;

        public TimerMiddleware(RequestDelegate next, TimeService timeService)
        {
            _next = next;
            _timeService = timeService;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            if (context.Request.Path.Value.ToLower() == "/time")
            {
				context.Response.ContentType = "text/html; charset=utf-8";
                await context.Response.WriteAsync($"Текущее время: {_timeService?.Time}");
            }
            else
            {
                await _next.Invoke(context);
            }
        }
    }
}

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

Логика компонента предполагает, что, если строка запроса равна строке "time", то с помощью TimeService возвращается текущее время. Иначе мы просто обращаемся к следующему делегату в конвейере обработки запроса.

Используем этот компонент в классе Startup:

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

namespace DIApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<TimeService>();
        }
        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<TimerMiddleware>();
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

В итоге, если мы используем в адресной строке путь "time", то приложение выведет текущее время:

Сервисы в middleware в ASP.NET Core

Однако сколько бы мы раз не обращались по этому пути, мы все время будем получать одно и то же время, так как объект TimerMiddleware был создан еще при первом запросе. Поэтому передача через конструктор middleware больше подходит для сервисов с жизненным циклом Singleton, которые создаются один раз для всех последующих запросов.

Если же в middleware необходимо использовать сервисы с жизненным циклом Scoped или Transient, то лучше их передавать через параметр метода Invoke/InvokeAsync:

public class TimerMiddleware
{
	private readonly RequestDelegate _next;

	public TimerMiddleware(RequestDelegate next)
	{
		_next = next;
	}

	public async Task InvokeAsync(HttpContext context, TimeService timeService)
	{
		if (context.Request.Path.Value.ToLower() == "/time")
		{
			context.Response.ContentType = "text/html; charset=utf-8";
			await context.Response.WriteAsync($"Текущее время: {timeService?.Time}");
		}
		else
		{
			await _next.Invoke(context);
		}
	}
}

Также можно получать сервисы через свойство HttpContext.RequestServices в тех ситуациях, когда необходимость использования сервисов может зависеть от среды выполнения:

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

namespace DIApp
{
    public class TimerMiddleware
    {
        private readonly RequestDelegate _next;

        public TimerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            context.Response.ContentType = "text/html; charset=utf-8";
            if (context.Request.Path.Value.ToLower() == "/time")
            {
                TimeService timeService = context.RequestServices.GetService<TimeService>();
                await context.Response.WriteAsync($"Текущее время: {timeService?.Time}");   
            }
            else
            {
                await context.Response.WriteAsync($"Параметр неопределен");
            }
        }
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850