Компоненты 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, но об этом мы поговорим позднее)