Middleware со строгой типизацией в ASP.NET Core
Большинство разработчиков ASP.NET Core знакомы с традиционным способом создания классов ПО промежуточного слоя, основанным на соглашениях. В подавляющем большинстве примеров используется подход, основанный на соглашениях. Но также доступен строго типизированный подход, основанный на реализации IMiddleware
интерфейса. Кажется, это один из самых сокровенных секретов ASP.NET Core, поэтому я подумал, что стоит отдернуть занавески и позволить пролить свет на это.
Во-первых, давайте напомним себе, как работает промежуточное ПО, основанное на соглашениях. Два соглашения, которые должны быть применены к основанному на соглашениях классу ПО промежуточного слоя, заключаются в объявлении конструктора, принимающего a RequestDelegate
в качестве параметра, представляющего следующее ПО промежуточного слоя в конвейере, и метода с именем Invoke
или InvokeAsync
, который возвращает a Task
и имеет по крайней мере один параметр, первый будучи HttpContext
.
Следующий код иллюстрирует класс промежуточного программного обеспечения, который реализует эти соглашения и регистрирует значение IP-адреса посетителя:
public class ConventionalIpAddressMiddleware
{
private readonly RequestDelegate _next;
public ConventionalIpAddressMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context, ILogger<ConventionalIpAddressMiddleware> logger)
{
var ipAddress = context.Connection.RemoteIpAddress;
logger.LogInformation("Visitor is from {ipAddress}", ipAddress);
await _next(context);
}
}
- Конструктор принимает в
RequestDelegate
качестве параметра - Метод
InvokeAsync
возвращает задачу и имеетHttpContext
в качестве первого параметра. Любые дополнительные сервисы внедряются вInvoke/InvokeAsync
метод послеHttpContext
- Обработка происходит внутри
InvokeAsync
метода - Вызывается
RequestDelegate
, передавая управление следующему промежуточному программному обеспечению в конвейере.
Класс промежуточного программного обеспечения добавляется в конвейер с помощью универсального UseMiddleware
метода IApplicationBuilder
:
app.UseMiddleware<ConventionalIpAddressMiddleware>();
В этом конкретном случае вы, вероятно, захотите зарегистрировать это промежуточное ПО после промежуточного ПО для статических файлов, чтобы оно не регистрировало IP-адрес одного и того же посетителя для каждого запрошенного файла.
ПО промежуточного слоя, которое следует подходу, основанному на соглашениях, создается как одноэлементное при первом запуске приложения, а это означает, что на все время существования приложения создается только один экземпляр. Этот экземпляр используется для каждого запроса, который достигает его. Если ваше промежуточное ПО опирается на ограниченные или временные зависимости, вы должны внедрить их через Invoke
метод, чтобы они разрешались каждый раз, когда метод вызывается платформой. Если вы внедрите их через конструктор, они будут захвачены как singleton. Регистратор в этом примере сам по себе является singleton, поэтому его можно предоставить через Invoke
метод или конструктор.
Внедрение IMiddleware
Альтернативный подход к написанию классов промежуточного ПО включает реализацию IMiddleware
интерфейса. Интерфейс IMiddleware
предоставляет один метод:
Task InvokeAsync(HttpContext context, RequestDelegate next)
Вот IpAddressMiddleware, реализованный как IMiddleware
:
public class IMiddlewareIpAddressMiddleware : IMiddleware
{
private readonly ILogger<IMiddlewareIpAddressMiddleware> _logger;
public IMiddlewareIpAddressMiddleware(ILogger<IMiddlewareIpAddressMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var ipAddress = context.Connection.RemoteIpAddress;
_logger.LogInformation("Visitor is from {ipAddress}", ipAddress);
await next(context);
}
}
- Класс промежуточного программного обеспечения реализует
IMiddleware
интерфейс - Зависимости внедряются в конструктор
InvokeAsync
принимаетHttpContext
и вRequestDelegate
качестве параметров
Реализация InvokeAsync
очень похожа на ту, что была написана с использованием подхода, основанного на соглашениях, за исключением того, что на этот раз параметрами являются HttpContext
и RequestDelegate
. Любые службы, от которых зависит класс, внедряются через конструктор класса промежуточного ПО, поэтому поля необходимы для хранения экземпляров внедряемой службы. Это промежуточное ПО регистрируется точно так же, как и пример, основанный на соглашениях, с помощью UseMiddleware
методов или метода расширения:
app.UseMiddleware<IMiddlewareIpAddressMiddleware>();
Но для основанных компонентов также требуется дополнительный шаг IMiddleware
— они также должны быть зарегистрированы в сервис-контейнере приложения. В этом примере промежуточное ПО регистрируется с ограниченным сроком службы.
builder.Services.AddScoped<IMiddlewareIpAddressMiddleware>();
Так почему же существует два разных способа создания классов промежуточного программного обеспечения и какой из них следует использовать? Что ж, подход, основанный на соглашениях, требует, чтобы вы выучили конкретные соглашения и запомнили их. Нет проверки во время компиляции, чтобы убедиться, что промежуточное ПО правильно реализует соглашения. Этот подход известен как слабо типизированный. Как правило, в первый раз вы обнаруживаете, что забыли назвать свой метод Invoke
или InvokeAsync
, или что первым параметром должен быть HttpContext
, когда вы пытаетесь запустить приложение, и оно падает. Если вы чем-то похожи на меня, вы часто обнаруживаете, что вам приходится обращаться к документации, чтобы напомнить себе о деталях соглашения, особенно если вы не так часто создаете промежуточное ПО.
Второй подход приводит к строго типизированному промежуточному программному обеспечению, потому что вам необходимо реализовать элементы IMiddleware
интерфейса, иначе компилятор выдаст ошибку, и ваше приложение даже не будет собрано. Таким образом, этот IMiddleware
подход менее подвержен ошибкам и, возможно, быстрее реализуется, хотя вам придется предпринять дополнительный шаг по регистрации промежуточного программного обеспечения в сервис-контейнере.
Есть еще одно различие между этими двумя подходами. Ранее я упоминал, что ПО промежуточного слоя на основе соглашений всегда создается как одноэлементный экземпляр при первом построении конвейера. IMiddleware
компоненты извлекаются из сервисного контейнера и создаются всякий раз, когда они необходимы компоненту, реализующему IMiddlewareFactory
интерфейс, и это различие имеет последствия для сервисов, от которых зависит промежуточное ПО, в зависимости от их зарегистрированного срока службы. В этом примере промежуточное ПО имеет ограниченное время жизни, поэтому оно может безопасно принимать службы с заданной областью через свой конструктор.
Следует отметить, что большая часть существующего промежуточного программного обеспечения фреймворка создается с использованием подхода, основанного на соглашениях. В основном это связано с тем, что большая часть этого была написана до того, как IMiddleware
была представлена в .NET Core 3.0. Сказав это, нет никаких признаков того, что разработчики фреймворка чувствуют необходимость переноса существующего промежуточного программного обеспечения на IMiddleware
.
Только полноправные пользователи могут оставлять комментарии. Аутентифицируйтесь пожалуйста, используя сервисы.