Cara membaca ASP.NET Inti Respon.Tubuh?

I've telah berjuang untuk mendapatkan Respon.Tubuhproperti dari ASP.NET Inti aksi dan satu-satunya solusi I've telah mampu mengidentifikasi tampaknya sub-optimal. Solusi membutuhkan swapping outRespon.TubuhdenganMemoryStreamsaat membaca stream ke dalam variabel string, kemudian menukar kembali sebelum dikirim ke client. Dalam contoh di bawah ini, saya'm mencoba untuk mendapatkan Respon.Tubuh nilai di custom middleware kelas. Respon.Tubuh adalah set satu-satunya properti di ASP.NET Inti untuk beberapa alasan? Aku hanya kehilangan sesuatu di sini, atau ini merupakan pengawasan/bug/masalah desain? Apakah ada cara yang lebih baik untuk membaca Respon.Tubuh`?

Saat ini (sub-optimal) solusi:

public class MyMiddleWare
{
    private readonly RequestDelegate _next;

    public MyMiddleWare(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        using (var swapStream = new MemoryStream())
        {
            var originalResponseBody = context.Response.Body;

            context.Response.Body = swapStream;

            await _next(context);

            swapStream.Seek(0, SeekOrigin.Begin);
            string responseBody = new StreamReader(swapStream).ReadToEnd();
            swapStream.Seek(0, SeekOrigin.Begin);

            await swapStream .CopyToAsync(originalResponseBody);
            context.Response.Body = originalResponseBody;
        }
    }
}  

Berusaha larutan menggunakan EnableRewind(): Ini hanya bekerja untuk Permintaan.Tubuh, bukan Respon.Tubuh. Ini hasil dalam membaca sebuah string kosong dari Respon.Tubuh daripada yang sebenarnya respon tubuh isi.

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifeTime)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

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

    app.UseMyMiddleWare();

    app.UseMvc();

    // Dispose of Autofac container on application stop
    appLifeTime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}

MyMiddleWare.cs

public class MyMiddleWare
{
    private readonly RequestDelegate _next;

    public MyMiddleWare(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await _next(context);
        string responseBody = new StreamReader(context.Request.Body).ReadToEnd(); //responseBody is ""
        context.Request.Body.Position = 0;
    }
}  
Mengomentari pertanyaan (1)
Larutan

Di awal saya respon saya telah benar-benar salah membaca pertanyaan dan pemikiran poster bertanya bagaimana membaca Permintaan.Tubuh Tapi dia bertanya bagaimana membaca Respon.Tubuh. I'm meninggalkan saya asli jawab untuk melestarikan sejarah, tetapi juga memperbarui itu untuk menunjukkan bagaimana saya akan menjawab pertanyaan setelah membaca dengan benar.

Jawaban asli

Jika anda ingin buffered aliran yang mendukung membaca beberapa kali anda perlu untuk mengatur

   context.Request.EnableRewind()

Idealnya lakukan ini di awal middleware sebelum apa-apa yang perlu dibaca dalam tubuh.

Jadi misalnya anda bisa letakkan kode berikut di awal Configure metode Startup.cs file:

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

Sebelum mengaktifkan Mundur stream yang terkait dengan Permintaan.Tubuh maju-satunya aliran yang doesn't dukungan yang mencari atau membaca aliran yang kedua kalinya. Hal ini dilakukan untuk membuat konfigurasi default dari penanganan permintaan seperti yang ringan dan performant mungkin. Tapi setelah anda mengaktifkan mundur stream upgrade ke aliran yang mendukung mencari dan membaca beberapa kali. Anda dapat mengamati hal ini "meng-upgrade" dengan mengatur breakpoint sebelum dan setelah panggilan untuk EnableRewind dan mengamati Permintaan.Tubuh properties. Jadi misalnya Permintaan.Tubuh.CanSeek akan berubah dari palsu untuk benar.

update: Mulai ASP.NET Inti 2.1 Permintaan.EnableBuffering() tersedia yang upgrade Permintaan.Tubuh ke FileBufferingReadStream seperti Permintaan.EnableRewind() dan karena Permintaan.EnableBuffering() adalah di publik namespace daripada internal salah satu hal yang harus diutamakan di atas EnableRewind(). (Terima kasih untuk @ArjanEinbu untuk menunjuk keluar)

Kemudian untuk membaca tubuh stream anda bisa misalnya melakukan hal ini:

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

Don't bungkus StreamReader penciptaan dalam menggunakan pernyataan atau meskipun itu akan menutup mendasari tubuh stream pada akhir menggunakan blok dan kode kemudian dalam siklus hidup tidak akan mampu membaca tubuh.

Juga hanya untuk menjadi aman, itu mungkin ide yang baik untuk mengikuti instruksi di atas baris kode yang berbunyi tubuh isi dengan baris kode untuk reset tubuh's stream posisi kembali ke 0.

request.Body.Position = 0;

Dengan cara itu setiap kode kemudian dalam siklus hidup akan menemukan permintaan.Tubuh dalam keadaan seperti ini masih't pernah baca belum.

Diperbarui Jawaban

Maaf saya awalnya salah membaca pertanyaan anda. Konsep upgrade terkait aliran menjadi buffered stream masih berlaku. Namun anda harus melakukannya secara manual, saya'm tidak menyadari apapun yang dibangun di .Net fungsionalitas Inti yang memungkinkan anda membaca respon stream sekali tertulis dalam cara yang EnableRewind() memungkinkan seorang pengembang membaca permintaan stream setelah itu's telah baca.

Anda "hacky" pendekatan ini mungkin benar-benar sesuai. Anda pada dasarnya mengkonversi aliran yang dapat't berusaha untuk salah satu yang bisa. Pada akhir hari Respon.Tubuh stream telah mendapatkan bertukar keluar dengan aliran yang buffered dan mendukung apapun. Berikut ini adalah mengambil middleware untuk melakukan itu, tetapi anda akan melihat itu's sangat mirip dengan pendekatan anda. Namun aku memilih untuk menggunakan akhirnya blok sebagai perlindungan tambahan untuk menempatkan aliran asli kembali pada Respon.Tubuh dan saya menggunakan Posisi milik stream daripada Mencari metode sejak sintaks ini sedikit lebih sederhana tetapi efeknya tidak berbeda dari pendekatan anda.

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;
            }

        } 
Komentar (17)

Apa yang anda jelaskan sebagai hack ini benar-benar disarankan pendekatan tentang bagaimana untuk mengelola respon sungai di custom middleware.

Karena pipa sifat middle ware desain di mana masing-masing tengah ware tidak menyadari sebelumnya atau berikutnya handler di dalam pipa. Tidak ada jaminan bahwa saat ini tengah ware akan menjadi salah satu yang menulis respon kecuali berpegang pada respon aliran itu diberikan sebelum lewat di sungai itu (saat ini tengah ware) kontrol. Desain ini terlihat di OWIN dan akhirnya dipanggang dalam asp.net-core.

Setelah anda mulai menulis untuk respon aliran ini mengirimkan body dan header (respon) untuk klien. Jika yang lain handler bawah pipa itu sebelum saat handler punya kesempatan maka itu tidak akan mampu menambahkan sesuatu untuk respon setelah itu telah sudah dikirim.

Yang lagi-lagi tidak dijamin untuk menjadi aktual respon stream jika sebelumnya middleware dalam pipa mengikuti strategi yang sama lewat aliran lain ke bawah garis.

Referensi ASP.NET Inti Middleware dasar-Dasar

Peringatan

hati-hati memodifikasi HttpResponse setelah menyerukan next, karena respon yang mungkin telah dikirim ke klien. Anda dapat menggunakan HttpResponse.HasStarted untuk memeriksa apakah header sudah dikirim.

Peringatan

Jangan panggil berikutnya.Memanggil setelah memanggil menulis metode. Sebuah middleware komponen baik menghasilkan respon atau panggilan berikutnya.Memanggil, tapi tidak keduanya.

Contoh dibangun di dasar middlewares dari aspnet/BasicMiddleware Github repo

ResponseCompressionMiddleware.cs


/// 
/// Invoke the middleware.
/// 
/// <param name="context">
Komentar (0)

Anda dapat menggunakan middleware dalam permintaan pipa, dalam rangka untuk log permintaan dan tanggapan.

Namun peningkatan bahaya kebocoran memori, karena facth bahwa:

  1. Sungai,
  2. Pengaturan Byte Buffer dan
  3. Konversi String

dapat berakhir dengan Tumpukan Besar Objek (dalam kasus tubuh dari permintaan atau respon yang lebih besar dari 85.000 byte). Hal ini meningkatkan bahaya dari kebocoran memori dalam aplikasi anda. Dalam rangka untuk menghindari LOH, memori aliran dapat diganti dengan Didaur ulang Memori streaming menggunakan relevan perpustakaan.

Sebuah implementasi yang menggunakan Didaur ulang memori aliran:

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;
    }
}

NB. Bahaya LOH ini tidak sepenuhnya memberantas karena textWriter.ToString() di sisi lain anda dapat menggunakan login client library yang mendukung terstruktur logging (ie. Serilog) dan inject contoh yang dapat Didaur ulang Memori Streaming.

Komentar (2)