TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.74K subscribers
Post #3335 1.09K
День 2787. #ЗаметкиНаПолях #Middleware
Что Такое Промежуточное ПО и Его Подводные Камни. Начало
В этой серии постов разберём, что представляет собой промежуточное ПО, как написать собственный компонент и с какими неожиданными проблемами могут столкнуться разработчики.

Создание промежуточного ПО
Промежуточное ПО добавляется в файле 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
  • 👍 7
More from @netdeveloperdiary
  1. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  2. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  3. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  4. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  5. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 21, 2026🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →