Что Такое Промежуточное ПО и Его Подводные Камни. Начало
В этой серии постов разберём, что представляет собой промежуточное ПО, как написать собственный компонент и с какими неожиданными проблемами могут столкнуться разработчики.
Создание промежуточного ПО
Промежуточное ПО добавляется в файле Program.cs с помощью метода
app.Use():app.Use(async (ctx, next) =>
{
Console.WriteLine($"Запрос: {ctx.Request.Path}");
await next(ctx);
Console.WriteLine($"Ответ: {ctx.Response.StatusCode}");
});
Промежуточное ПО выполняется при каждом HTTP-запросе. Каждый запрос проходит через него на входе, а ответ — на выходе.
Вызов
next() играет ключевую роль: он обеспечивает переход к следующему компоненту в конвейере обработки. Если забыть его добавить, выполнение конвейера остановится, и все последующие компоненты промежуточного ПО не будут запущены.Недостаток подхода с
app.Use() в использовании лямбда-выражения, что затрудняет тестирование. Более удачное решение — вынести логику в отдельный класс:public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
public RequestLoggingMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext ctx)
{
Console.WriteLine($"Запрос: {ctx.Request.Path}");
await _next(ctx);
Console.WriteLine($"Ответ: {ctx.Response.StatusCode}");
}
}
Не забывайте внедрять
next в конструктор, чтобы использовать его для вызова следующего компонента промежуточного ПО в конвейере.Затем зарегистрируйте этот компонент в файле Program.cs; вызов
app.Use(…) можно удалить:app.UseMiddleware<RequestLoggingMiddleware>();
Если не зарегистрировать класс, промежуточное ПО никогда не выполнится.
Порядок регистрации промежуточного ПО в Program.cs имеет значение. Если мы зарегистрируем RequestLoggingMiddleware первым, этот компонент первым обработает запрос, а ответ – последним:
app.UseMiddleware<RequestLoggingMiddleware>();
app.UseSwaggerUI(opts =>
{
opts.SwaggerEndpoint("/openapi/v1.json", "Api");
});
app.MapOpenApi();
var group = app.MapGroup("/api");
group.MapGet("test", () =>
TypedResults.Content("Это тест", "text/plain"););
Если забыть вызвать next()
public class RequestLoggingMiddleware
{
…
public async Task InvokeAsync(HttpContext ctx)
{
Console.WriteLine($"Запрос: {ctx.Request.Path}");
//await _next(ctx);
Console.WriteLine($"Response: {ctx.Response.StatusCode}");
}
}
При запуске приложения страница не отобразится. При вызове конечной точки API через Postman вместо строки «Это тест» возвращается пустой ответ со статусом 200.
Это происходит из-за того, что
RequestLoggingMiddleware зарегистрирован первым. Если он прерывает цепочку и не вызывает next(), то ни один из последующих компонентов промежуточного ПО, включая эндпоинты API и Swagger, не выполнится. Это полезно в некоторых случаях, когда вы хотите прервать обработку в зависимости от некоторого условия. Однако чаще всего необходимо вызывать next().Промежуточное ПО — это синглтон, а InvokeAsync имеет время жизни scoped
Зарегистрируйте scoped-сервис в Program.cs:
builder.Services.AddScoped<IMyScopedService, MyScopedService>();
Теперь внедрите его в RequestLoggingMiddleware.
public class RequestLoggingMiddleware
{
//…
public RequestLoggingMiddleware(
RequestDelegate next,
IMyScopedService mySvc)
{
//…
}
//…
}
При запуске приложения возникает ошибка: Cannot resolve scoped service 'IMyScopedService' from root provider (Не удаётся разрешить scoped-сервис IMyScopedService из корневого провайдера).
Это происходит потому, что метод UseMiddleware создаёт экземпляр класса промежуточного ПО один раз, используя корневой провайдер сервисов приложения, и повторно использует этот же экземпляр для каждого запроса. Сервис с областью видимости scoped нельзя получить напрямую из корневого провайдера.
Решение в том, чтобы убрать IMyScopedService из конструктора и внедрять его непосредственно в метод InvokeAsync:
public class RequestLoggingMiddleware
{
//…
public async Task InvokeAsync(
HttpContext context,
IMyScopedService mySvc)
{
//…
}
}
Метод InvokeAsync выполняется заново для каждого запроса, поэтому ASP.NET Core каждый раз разрешает IMyScopedService из области видимости текущего запроса.
Окончание следует…
Источник: https://www.roundthecode.com/dotnet-tutorials/what-is-middleware-aspnet-core-and-gotchas