Компоненты middleware можно создавать, как в форме делегатов (inline middleware), так и в виде отдельных классов. При создании класса middleware мы использовали т.н. активацию middleware по соглашению, когда к создаваемом классу предъявляются определенные требования к конструктору, реализуемым методам и т.д. Вместе с этим в ASP.NET Core, начиная с версии 2.0 появилась третья возможность создания компонентов middleware — на базе фабрики классов (factory-based middleware).
Интерфейс IMiddleware
Интерфейс IMiddleware
представляет собой компонент middleware, создаваемый на базе фабрики классов. Этот интерфейс содержит всего один метод:
Task InvokeAsync(HttpContext context, RequestDelegate next);
в котором происходит обработка запроса на основании контекста (context
) и, при необходимости, вызывается следующий компонент middleware (next
). Посмотрим, в чем отличия между вариантом активации middleware по соглашению и factory-based middleware. Для этого вспомним класс CharsetMiddleware
, который мы разрабатывали ранее:
public class CharsetMiddleware { private RequestDelegate _next; public CharsetMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var charset = context.Request.Query["charset"]; context.Response.OnStarting( () => { if (string.IsNullOrWhiteSpace(charset)) context.Response.ContentType = "text/plain; charset=utf-8"; else context.Response.ContentType = $"text/plain; charset={charset}"; return Task.CompletedTask; }); await _next(context); } }
Попробуем переписать его, используя интерфейс IMiddleware
, следующим образом:
public class CharsetFactoryMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var charset = context.Request.Query["charset"]; context.Response.OnStarting( () => { if (string.IsNullOrWhiteSpace(charset)) context.Response.ContentType = "text/plain; charset=utf-8"; else context.Response.ContentType = $"text/plain; charset={charset}"; return Task.CompletedTask; }); await next(context); } }
здесь RequestDelegate
передается уже не в конструкторе, а в реализуемом методе InvokeAsync
. Теперь этот middleware надо каким-то образом активировать.
Использование factory-based middleware
Чтобы компонент созданный на основе фабрики классов заработал, необходимо выполнить следующие действия:
1. Зарегистрировать класс middleware, как сервис (про сервисы и Dependency Injection говориться далее, поэтому пока — просто повторяем код):
var builder = WebApplication.CreateBuilder(args); builder.Services.AddTransient<CharsetFactoryMiddleware>();//регистрируем сервис var app = builder.Build();
регистрация сервиса должна осуществляться до вызова метода builder.Build()
.
2. Использовать метод UseMiddleware
, как мы делали ранее:
app.UseMiddleware<CharsetFactoryMiddleware>();
или использовать метод расширения для IApplicationBuilder
.
Ниже представлен весь код приложения, использующего factory-based middleware
namespace FactoryBasedMiddleware { public class CharsetFactoryMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var charset = context.Request.Query["charset"]; context.Response.OnStarting( () => { if (string.IsNullOrWhiteSpace(charset)) context.Response.ContentType = "text/plain; charset=utf-8"; else context.Response.ContentType = $"text/plain; charset={charset}"; return Task.CompletedTask; }); await next(context); } } public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddTransient<CharsetFactoryMiddleware>();//регистрируем сервис var app = builder.Build(); app.UseMiddleware<CharsetFactoryMiddleware>(); //используем middleware app.Run(async context => { await context.Response.WriteAsync("Привет, мир"); }); app.Run(); } } }
Ограничения
К сожалению, при использовании factory-based middleware имеется ограничение, заключающееся в том, что при использовании метода UseMiddleware<T>()
мы не можем использовать параметры. Например, следующий код:
public class CharsetFactoryMiddleware : IMiddleware { private string _defaultCharset; public CharsetFactoryMiddleware(string defaultCharset) { _defaultCharset = defaultCharset; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { ..... } } public class Program { public static void Main(string[] args) { ..... app.UseMiddleware<CharsetFactoryMiddleware>("win-1251"); .... app.Run(); } }
вызовет ошибку.
Итого
Для создание middleware на базе фабрики классов необходимо реализовать интерфейс IMiddleware
, имеющий всего один метод. Преимуществом такого подхода к построению middleware можно отнести строгую типизацию. При этом, ограничением является то, что при использовании фабричного метода активации Middleware становится недоступной передача произвольных параметров в middleware (можно передавать только зависимости из контейнера DI, но об этом мы поговорим позднее)