Веб-версияОткрыть в Telegram

ПостДень 2788. #ЗаметкиНаПолях #Middleware

18 сентября 2026
.
.NET Разработчик
День 2788. #ЗаметкиНаПолях #Middleware Что Такое Промежуточное ПО и Его Подводные Камни. Окончание Начало Изменение ответа после начала его отправки В данном случае промежуточное ПО обновляет код ответа после возврата управления из вызова next(): public async Task InvokeAsync(HttpContext ctx) { Console.WriteLine($"Запрос: {ctx.Request.Path}"); await _next(ctx); // Изменение статуса и ответа ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await ctx.Response.WriteAsync( "<p>Эта страница недоступна</p>"); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); } Это приводит к возникновению исключения: System.InvalidOperationException: StatusCode cannot be set because the response has already started. (StatusCode нельзя установить, так как отправка ответа уже началась) После того как отправка ответа началась, изменить код состояния или снова записать данные в ответ уже невозможно. Однако можно выполнить необходимые действия непосредственно перед началом отправки, воспользовавшись событием context.Response.OnStarting: public async Task InvokeAsync(HttpContext ctx) { Console.WriteLine($"Request: {ctx.Request.Path}"); ctx.Response.OnStarting(async() => { if (ctx.Response.HasStarted) return; // Изменение статуса и ответа ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await ctx.Response.WriteAsync( "<p>Эта страница недоступна</p>"); }); await _next(context); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); } В методе OnStarting сначала проверяется свойство HasStarted, и, если оно уже имеет значение true, выполнение метода досрочно завершается. В противном случае в ответ выводится сообщение о том, что страница в данный момент недоступна. Удержание scoped-сервисов после завершения обработки запроса public class MyScopedService : IMyScopedService, IDisposable { public bool Disposed { get; private set; } public void Test() { _logger.LogInformation( "MyScopedService.Test() успешно выполнено"); } public void Dispose() { Disposed = true; } } Добавим делегат OnCompleted в промежуточное ПО и создадим новую фоновую задачу, которая будет выполнена с задержкой в 5 секунд: public class RequestLoggingMiddleware { //… public async Task InvokeAsync( HttpContext ctx, IMyScopedService mySvc) { _logger.LogInformation($"Запрос: {ctx.Request.Path}"); //… ctx.Response.OnCompleted(() => { var _ = Task.Run(async() => { await Task.Delay(5000); try { mySvc.Test(); } catch (Exception ex) { _logger.LogError(ex, ex.Message); } }); return Task.CompletedTask; }); //… } } Код выполняется, и на первый взгляд всё в порядке. Страница отображается корректно, а в логах видно, что метод MyScopedService.Test() срабатывает при каждом запросе. Однако здесь кроется скрытая проблема, зависящая от того, как именно вы используете этот сервис. Проблема в том, что после завершения формирования ответа scoped-сервис уже оказывается утилизированным (disposed), поскольку он был внедрён в метод InvokeAsync. Чтобы продемонстрировать это, добавьте в MyScopedService проверку, которая будет выбрасывать исключение, если сервис уже утилизирован. public class MyScopedService : IMyScopedService, IDisposable { //… public void Test() { ObjectDisposedException.ThrowIf(Disposed, this); _logger.LogInformation( "MyScopedService.Test() успешно выполнено"); } //… } Страница по-прежнему отображается корректно, но в логах теперь исключение: Cannot access a disposed object (Попытка доступа к объекту, который уже был утилизирован). Более надёжный подход — создать новую область видимости для подобных фоновых задач, а не повторно использовать экземпляр, привязанный к области видимости текущего запроса: public class RequestLoggingMiddleware { //… public async Task InvokeAsync( HttpContext ctx, IMyScopedService mySvc, IServiceScopeFactory scopeFactory) { //… ctx.Response.OnCompleted(() => { var _ = Task.Run(async() => { // Создаём новую область видимости using var scope = _scopeFactory.CreateScope(); mySvc = scope.ServiceProvider .GetRequiredService<IMyScopedService>(); await Task.Delay(5000); try { mySvc.Test(); } catch (Exception ex) { _logger.LogError(ex, ex.Message); } _logger.LogInformation("Task completed"); }); return Task.CompletedTask; }); await _next(ctx); //… } } Благодаря внедрению IServiceScopeFactory в промежуточное ПО и созданию новой области видимости (scope) внутри фоновой задачи, mySvc теперь разрешается в рамках этой новой области, а не той, что привязана к исходному запросу; следовательно, сервис не уничтожается к моменту выполнения отложенной операции. Источник: https://www.roundthecode.com/dotnet-tutorials/what-is-middleware-aspnet-core-and-gotchas
16 · 1.3K ·

Рядом в ленте

..NET РазработчикДень 2786. #AI Пусть Copilot Поспорит с Вами Как разработчик или архитектор, вы ежедневно принимаете множество проектных решений. Вы выбираете Redis для кэширов..NET РазработчикДень 2787. #ЗаметкиНаПолях #Middleware Что Такое Промежуточное ПО и Его Подводные Камни. Начало В этой серии постов разберём, что представляет собой промежуточн
это сообщение
..NET РазработчикДень 2789. #Оффтоп #AI Важно ли По-прежнему Качество Кода? Автор оригинала: Марк Симан Пока мы используем LLM как инструмент для генерации кода, за который в ит..NET РазработчикДень 2790. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.
..NET Разработчик.NET Разработчик@NetDeveloperDiary · канал · Технологии
6 750подписчиков1 355средний охват поста
Лента площадки Открыть в Telegram

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем