Saltar al contenido

¿Cómo leer ASP.NET Core Response.Body?

Este team redactor ha estado largas horas investigando para dar solución a tus búsquedas, te dejamos la soluciones de modo que nuestro deseo es serte de mucha ayuda.

Solución:

En mi respuesta original, había malinterpretado totalmente la pregunta y pensé que el cartel preguntaba cómo leer la Request.Body Pero había preguntado cómo leer el Response.Body. Dejo mi respuesta original para preservar el historial, pero también la actualizo para mostrar cómo respondería la pregunta una vez que la lea correctamente.

Respuesta original

Si desea una transmisión almacenada en búfer que admita la lectura varias veces, debe configurar

   context.Request.EnableRewind()

Idealmente, haga esto al principio del middleware antes de que sea necesario leer el cuerpo.

Entonces, por ejemplo, podría colocar el siguiente código al comienzo de la Configure método del archivo Startup.cs:

        app.Use(async (context, next) => 
            context.Request.EnableRewind();
            await next();
        );

Antes de habilitar Rebobinar la secuencia asociada con el Request.Body es una transmisión de solo avance que no admite la búsqueda o lectura de la transmisión por segunda vez. Esto se hizo para que la configuración predeterminada del manejo de solicitudes sea lo más liviana y eficiente posible. Pero una vez que habilita el rebobinado, la transmisión se actualiza a una transmisión que admite la búsqueda y lectura varias veces. Puede observar esta “actualización” estableciendo un punto de interrupción justo antes y después de la llamada a EnableRewind y observando el Request.Body propiedades. Así por ejemplo Request.Body.CanSeek cambiará de false para true.

actualizar: A partir de ASP.NET Core 2.1 Request.EnableBuffering() está disponible que actualiza el Request.Body a un FileBufferingReadStream al igual que Request.EnableRewind() y desde Request.EnableBuffering() está en un espacio de nombres público en lugar de uno interno, debería preferirse a EnableRewind (). (Gracias a @ArjanEinbu por señalar)

Luego, para leer el flujo del cuerpo, podría, por ejemplo, hacer esto:

   string bodyContent = new StreamReader(Request.Body).ReadToEnd();

No envuelvas el StreamReader Sin embargo, la creación en una declaración de uso o cerrará el flujo del cuerpo subyacente al final del bloque de uso y el código más adelante en el ciclo de vida de la solicitud no podrá leer el cuerpo.

Además, solo para estar seguro, podría ser una buena idea seguir la línea de código anterior que lee el contenido del cuerpo con esta línea de código para restablecer la posición del flujo del cuerpo de nuevo a 0.

request.Body.Position = 0;

De esa manera, cualquier código posterior en el ciclo de vida de la solicitud encontrará la solicitud. Cuerpo en un estado en el que aún no se ha leído.

Respuesta actualizada

Lo siento, originalmente leí mal tu pregunta. El concepto de actualizar el flujo asociado para que sea un flujo almacenado en búfer todavía se aplica. Sin embargo, tiene que hacerlo manualmente, no conozco ninguna funcionalidad .Net Core incorporada que le permita leer el flujo de respuesta una vez escrito de la manera que EnableRewind() permite que un desarrollador vuelva a leer la secuencia de solicitudes después de que se haya leído.

Es probable que su enfoque “hacky” sea totalmente apropiado. Básicamente, está convirtiendo una secuencia que no puede buscar en una que sí puede. Al final del día, el Response.Body la secuencia debe intercambiarse con una secuencia que esté almacenada en búfer y admita la búsqueda. Aquí hay otra versión de middleware para hacer eso, pero notará que es bastante similar a su enfoque. Sin embargo, elegí usar un bloque final como protección adicional para volver a poner la transmisión original en el Response.Body y usé el Position propiedad de la corriente en lugar de la Seek método ya que la sintaxis es un poco más simple, pero el efecto no es diferente al de su enfoque.

public class ResponseRewindMiddleware 

        private readonly RequestDelegate next;

        public ResponseRewindMiddleware(RequestDelegate next) 
            this.next = next;
        

        public async Task Invoke(HttpContext context) 

            Stream originalBody = context.Response.Body;

            try 
                using (var memStream = new MemoryStream()) 
                    context.Response.Body = memStream;

                    await next(context);

                    memStream.Position = 0;
                    string responseBody = new StreamReader(memStream).ReadToEnd();

                    memStream.Position = 0;
                    await memStream.CopyToAsync(originalBody);
                

             finally 
                context.Response.Body = originalBody;
            

         

Lo que describe como un truco es en realidad el enfoque sugerido de cómo administrar los flujos de respuesta en el middleware personalizado.

Debido a la naturaleza de la canalización del diseño de los productos intermedios, donde cada producto intermedio desconoce el controlador anterior o siguiente en la canalización. No hay garantía de que el middleware actual sea el que escriba la respuesta a menos que se aferre al flujo de respuesta que se le dio antes de pasar un flujo que controla (el middleware actual). Este diseño se vio en OWIN y finalmente se incorporó a asp.net-core.

Una vez que comienzas a escribir en el flujo de respuesta, envía el cuerpo y los encabezados (la respuesta) al cliente. Si otro controlador en la tubería hace eso antes de que el controlador actual tenga la oportunidad de hacerlo, no podrá agregar nada a la respuesta una vez que ya se haya enviado.

De nuevo, no se garantiza que sea el flujo de respuesta real si el middleware anterior en la canalización siguió la misma estrategia de pasar otro flujo en la línea.

Referencia a ASP.NET Core Middleware Fundamentals

Advertencia

Tenga cuidado al modificar el HttpResponse después de invocar next, porque es posible que la respuesta ya se haya enviado al cliente. Puede utilizar HttpResponse.HasStarted para comprobar si se han enviado los encabezados.

Advertencia

No llames next.Invoke después de llamar a un write método. Un componente de middleware produce una respuesta o llama next.Invoke, pero no ambos.

Ejemplo de middlewares básicos integrados de aspnet / BasicMiddleware Repositorio de Github

ResponseCompressionMiddleware.cs

/// 
/// Invoke the middleware.
/// 
/// 
/// 
public async Task Invoke(HttpContext context)

    if (!_provider.CheckRequestAcceptsCompression(context))
    
        await _next(context);
        return;
    

    var bodyStream = context.Response.Body;
    var originalBufferFeature = context.Features.Get();
    var originalSendFileFeature = context.Features.Get();

    var bodyWrapperStream = new BodyWrapperStream(context, bodyStream, _provider,
        originalBufferFeature, originalSendFileFeature);
    context.Response.Body = bodyWrapperStream;
    context.Features.Set(bodyWrapperStream);
    if (originalSendFileFeature != null)
    
        context.Features.Set(bodyWrapperStream);
    

    try
    
        await _next(context);
        // This is not disposed via a using statement because we don't want to flush the compression buffer for unhandled exceptions,
        // that may cause secondary exceptions.
        bodyWrapperStream.Dispose();
    
    finally
    
        context.Response.Body = bodyStream;
        context.Features.Set(originalBufferFeature);
        if (originalSendFileFeature != null)
        
            context.Features.Set(originalSendFileFeature);
        
    

Puede utilizar un middleware en la canalización de solicitudes para registrar solicitudes y respuestas.

Sin embargo, aumenta el riesgo de memory leak, debido al hecho de que: 1. Flujos, 2. Configuración de búferes de bytes y 3. Conversiones de cadenas

puede terminar en un montón de objetos grandes (en caso de que el cuerpo de la solicitud o la respuesta supere los 85.000 bytes). Esto aumenta el riesgo de pérdida de memoria en su aplicación. Para evitar LOH, los flujos de memoria se pueden reemplazar por flujo de memoria reciclable utilizando la biblioteca correspondiente.

Una implementación que utiliza flujos de memoria reciclable:

public class RequestResponseLoggingMiddleware

    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
    private const int ReadChunkBufferLength = 4096;

    public RequestResponseLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
    
        _next = next;
        _logger = loggerFactory
            .CreateLogger();
        _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
    

    public async Task Invoke(HttpContext context)
    
        LogRequest(context.Request);
        await LogResponseAsync(context);
    

    private void LogRequest(HttpRequest request)
    
        request.EnableRewind();
        using (var requestStream = _recyclableMemoryStreamManager.GetStream())
        
            request.Body.CopyTo(requestStream);
            _logger.LogInformation($"Http Request Information:Environment.NewLine" +
                                   $"Schema:request.Scheme " +
                                   $"Host: request.Host " +
                                   $"Path: request.Path " +
                                   $"QueryString: request.QueryString " +
                                   $"Request Body: ReadStreamInChunks(requestStream)");
        
    

    private async Task LogResponseAsync(HttpContext context)
    
        var originalBody = context.Response.Body;
        using (var responseStream = _recyclableMemoryStreamManager.GetStream())
        
            context.Response.Body = responseStream;
            await _next.Invoke(context);
            await responseStream.CopyToAsync(originalBody);
            _logger.LogInformation($"Http Response Information:Environment.NewLine" +
                                   $"Schema:context.Request.Scheme " +
                                   $"Host: context.Request.Host " +
                                   $"Path: context.Request.Path " +
                                   $"QueryString: context.Request.QueryString " +
                                   $"Response Body: ReadStreamInChunks(responseStream)");
        

        context.Response.Body = originalBody;
    

    private static string ReadStreamInChunks(Stream stream)
    
        stream.Seek(0, SeekOrigin.Begin);
        string result;
        using (var textWriter = new StringWriter())
        using (var reader = new StreamReader(stream))
        
            var readChunk = new char[ReadChunkBufferLength];
            int readChunkLength;
            //do while: is useful for the last iteration in case readChunkLength < chunkLength
            do
            
                readChunkLength = reader.ReadBlock(readChunk, 0, ReadChunkBufferLength);
                textWriter.Write(readChunk, 0, readChunkLength);
             while (readChunkLength > 0);

            result = textWriter.ToString();
        

        return result;
    

NÓTESE BIEN. El peligro de LOH no se erradica completamente debido a textWriter.ToString() por otro lado, puede utilizar una biblioteca de cliente de registro que admita el registro estructurado (es decir, Serilog) e inyectar la instancia de un flujo de memoria reciclable.

Acuérdate de que puedes permitirte explicar si te fue de ayuda.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *