Промежуточное ПО .NET 8 неожиданно меняет код состояния на 204 после _next(context)

Я работаю над веб-приложением .NET 8. Я внедрил промежуточное программное обеспечение для регистрации запросов и ответов, но столкнулся с проблемой, когда код состояния неожиданно меняется с 200 на 204 после вызова _next(context). Вот код и подробное описание моей настройки и проблемы:

Реализация промежуточного программного обеспечения

public async Task Invoke(HttpContext context)
{
    var requestBody = await GetRequestBody(context.Request);
    var originalBodyStream = context.Response.Body;

    using (var memoryStream = new MemoryStream())
    {
        context.Response.Body = memoryStream;

        // Log initial headers and status code: StatusCode is 200 before request goes to handle.
        Console.WriteLine($"Initial Status Code: {context.Response.StatusCode}");

        await _next(context);

        // Log headers and status code after _next. StatusCode is 204 even though its is being set to 200 explicitly in handler and context.Response.Body is also not null/empty.
        Console.WriteLine($"Status Code After _next: {context.Response.StatusCode}");

        memoryStream.Seek(0, SeekOrigin.Begin);
        var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
        context.Response.Body = originalBodyStream;

        // Write the response body if it is not empty
        if (!string.IsNullOrEmpty(responseBody))
        {
            try
            {
                context.Response.ContentLength = Encoding.UTF8.GetByteCount(responseBody);
                //While writing responseBody it also throws following exception: Writing to the response body is invalid for responses with status code 204.
                await context.Response.WriteAsync(responseBody);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error writing response body: {ex.Message}");
            }
        }

        await memoryStream.CopyToAsync(originalBodyStream);
    }
}

Обработчик конечной точки

public class CreateAuthorization : Endpoint<CreateAuthorizationRequest, CreateAuthorizationResponse>
{
    public override void Configure()
    {
        Post(CreateAuthorizationRequest.Route);
        Summary(s =>
        {
            s.Summary = "Create a new Authorization request.";
            s.Description = "Create a new Authorization request to get random number from Nafath.";
        });
    }

    public override async Task HandleAsync(CreateAuthorizationRequest request, CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(new CreateAuthorizationCommand(request.IdNumber!, action, request.Service!, ""), cancellationToken);

        var convertedResult = result.IsSuccess ? new CreateAuthorizationResponse(result.Value.TransactionId, result.Value.Random) : Result.Error();

        await convertedResult.ToOkOrProblemDetail(HttpContext);

        //Till this point HttpContext.Response.StatusCode is 200 and after this it goes directly to middleware after the line `await _next(context);` and somehow status code changed to 204.
    }
}

Метод расширения ToOkOrProblemDetails

public static async Task ToOkOrProblemDetail(this Result result, HttpContext httpContext)
{
    if (result.IsSuccess)
    {
        var responseBody = JsonSerializer.Serialize(result.Value);
        httpContext.Response.StatusCode = StatusCodes.Status200OK;
        httpContext.Response.ContentType = "application/json";
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(responseBody);
        await httpContext.Response.WriteAsync(responseBody);
        Console.WriteLine("Response written with status 200 and content length set");
    }
    else
    {
        await result.ToProblemDetails(httpContext);
    }

}

Конвейер промежуточного программного обеспечения

В Program.cs промежуточное программное обеспечение настроено следующим образом:

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app. UseDeveloperExceptionPage() ;
    app.UseShowAIIServicesMiddIeware();
}
else
{
    app.UseDefauItExceptionHandIer();
    app.UseHsts();
}
app.UseAuthentication();
app.UseAuthorization();
app.UseFastEndpoints(x => x.Errors.UseProbIemDetails());

//Using the middleware right after configuration of FastEndpoints but moving this to right after builder.Build() or right before app.Run() have not affect on the error.
app.UseMiddleware<RequestLoggingMiddleware>();

app.UseSwaggerGen();
app.UseHttpsRedirection();
SeedDatabase(app);
if (GetLocaIIPAddress() == && builder.Configuration.GetValue<string>("AllowIp"))
    app.UseHangfireDashboard("/hangfire", new DashboardOptions
    {
        Authorization = new[] { new AllowAnonymousAccessFilter() }
    });

app.Run();

Подробности проблемы

Несмотря на то, что код состояния установлен на 200 в обработчике (фактически внутри метода расширения ToOkOrProblemDetail), после возврата к промежуточному программному обеспечению он меняется на 204. Это неожиданное изменение приводит к ошибкам при попытке записи в тело ответа: «Запись в тело ответа недопустима для ответов с кодом состояния 204».

Я был бы признателен за любые идеи или предложения о том, почему код состояния меняется с 200 на 204 после конвейера промежуточного программного обеспечения и как предотвратить это. Может ли использование библиотеки FastEndpoint повлиять на такое поведение?

Вот что я пробовал до сих пор:

  1. Копирование ответа: для копирования ответа использовались разные методы, включая context.Response.WriteAsync и context.Response.CopyAsync.

  2. Явное задание длины контента: попробовал явно установить Content-Length в обработчике.

  3. Размещение промежуточного программного обеспечения: промежуточное программное обеспечение перемещено в разные места в конвейере, чтобы увидеть, влияет ли размещение на поведение.

  4. Ручная установка кода состояния: Пробовал как устанавливать, так и не устанавливать код состояния вручную в промежуточном программном обеспечении.

Несмотря на эти попытки, код статуса по-прежнему меняется с 200 на 204 после _next(context).

Кода, связанного с _mediator и CreateAuthorizationCommand, нет, поэтому я не могу воспроизвести вашу проблему. Не могли бы вы предоставить более подробный код? Пробовали ли вы проверить, сохраняется ли проблема, без использования библиотеки FastEndpoint?

Yumiao Kong 16.07.2024 11:03

@YumiaoKong Спасибо за ваш отзыв. Я хотел сообщить вам, что проблема решена. Проблема действительно была связана с FastEndpoints. Используя метод HttpContext.MarkResponseStart() в своем методе расширения ToOkOrProblemDetail(), я смог сообщить FastEndpoints, что обрабатываю ответ, что решило проблему.

Asad 16.07.2024 16:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

при написании ответа в поток ответов самостоятельно, как вы это делаете в методе расширения ToOkOrProblemDetail(), вам необходимо сообщить fastdpoints, что вы позаботитесь об отправке ответа следующим образом:

HttpContext.MarkResponseStart()

это метод расширения, предоставляемый FE. это показано в документации здесь относительно написания собственных Send*Async() методов в качестве расширений.

Я считаю, что это должно решить проблему. если нет, создайте новую задачу либо в репозитории FE на GitHub, либо на сервере Discord с проектом воспроизведения с вашим кодом.

Большое спасибо за ваш ответ! Добавление HttpContext.MarkResponseStart() в мой метод расширения ToOkOrProblemDetail() сработало отлично. Ваше руководство о том, как сообщить FastEndpoints о том, что я обрабатываю ответ, было именно тем, что мне нужно.

Asad 16.07.2024 16:11

Другие вопросы по теме

Пользовательское промежуточное программное обеспечение Rails не распознается в GH CI
Ошибка промежуточного программного обеспечения Laravel: аргумент № 1 ($content) должен иметь тип ?string, задан Illuminate\Routing\Redirector
Есть ли способ передать мою константу middleware.ts, которая проверяет, вошел ли пользователь в другой API, логику домена или page.tsx? (также открыт для новых предложений)
Как написать промежуточное программное обеспечение или перехватчик SvelteKit для выборки как на сервере, так и на клиенте?
Регистрация промежуточного программного обеспечения Laravel, не делая его глобальным
Установите файл cookie запроса в промежуточном программном обеспечении NextJS v14
Как перенаправить исключение, перехваченное промежуточным программным обеспечением, обратно в метод действия контроллера в C# ASP.NET Core?
Получение страницы 404 при использовании NextResponse.next() в промежуточном программном обеспечении Next.js 14
Как исправить ошибку признака службы при использовании from_fn_with_state для промежуточного программного обеспечения?
ORM ИЛИ уровень очистки промежуточного программного обеспечения?