1. ホーム
  2. c#

[解決済み] ASP.NET CoreのResponse.Bodyを読み取るには?

2023-01-29 02:51:29

質問

私は Response.Body プロパティを取得するのに苦労しており、私が特定できた唯一の解決策は、最適とは言えないようです。 この解決策では Response.BodyMemoryStream を使いながら、ストリームを文字列変数に読み込んで、クライアントに送信する前にスワップバックしています。 以下の例では、ストリームを文字列変数に読み込む際に Response.Body の値をカスタムミドルウェアクラスで使用しています。 Response.Body セット は、何らかの理由で ASP.NET Core のみのプロパティですか? 私が見落としているだけなのか、それとも見落とし/バグ/設計上の問題なのでしょうか? を読み取る良い方法はありますか? Response.Body ?

現在の(最適でない)解決策です。

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

EnableRewind()を用いた解決策を試みました。 これは Request.Body に対してのみ動作し Response.Body . この結果、空の文字列を Response.Body から空文字列を読み取ることになります。

スタートアップ.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;
    }
}  

どのように解決するのですか?

最初の回答で、私は質問を完全に読み間違えていて、投稿者が Request.Body をどのように読むかを尋ねていたのです。 Response.Body . 私は歴史を保存するために元の答えを残していますが、一度正しく読んだらどのように答えるかを示すために更新しています。

元の回答

複数回の読み込みをサポートするバッファードストリームが必要な場合、以下のように設定する必要があります。

   context.Request.EnableRewind()

理想的には、ミドルウェアの早い段階で、何かがボディを読む必要がある前にこれを行うことです。

ですから、例えば次のようなコードを Configure メソッドの冒頭に記述します。

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

巻き戻しを有効にする前に Request.Body に関連付けられたストリームは、2 度目のシークや読み込みをサポートしない、 前進のみのストリームです。 これは、リクエスト処理のデフォルトの構成を可能な限り軽量で高性能なものにするために行われました。 しかし、一旦巻き戻しを有効にすると、ストリームは複数回のシークと読み込みをサポートするストリームにアップグレードされます。 を呼び出す直前と直後にブレークポイントを設定することで、 この "upgrade" を観察することができます。 EnableRewind の呼び出しの直前と直後にブレークポイントを設定し Request.Body のプロパティを観察します。 ですから、例えば Request.Body.CanSeekfalse から true .

更新 : ASP.NET Core 2.1での開始について Request.EnableBuffering() をアップグレードすることができます。 Request.BodyFileBufferingReadStream と同じように Request.EnableRewind() であり Request.EnableBuffering() は内部ではなく、パブリックな名前空間にあるので、EnableRewind() よりも優先されるべきです。 (指摘してくれた@ArjanEinbuに感謝します)

それから、ボディストリームを読むために、例えば次のようにすることができます。

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

をラップしないで StreamReader さもないと、using ブロックの終了時に基礎となるボディストリームが閉じられ、リクエストライフサイクルの後半にあるコードはボディを読むことができなくなります。

また、念のため、ボディの内容を読み取る上記のコード行の後に、ボディのストリーム位置を 0 に戻すこのコード行を続けるのもよい考えかもしれません。

request.Body.Position = 0;

そうすれば、リクエストのライフサイクルの後半にあるどんなコードも、まだ読まれていないような状態のrequest.Bodyを見つけることができるようになります。

更新された回答

最初に質問を読み間違えてしまい、申し訳ありません。関連するストリームをバッファード ストリームにアップグレードするコンセプトは、まだ適用されます。しかし、手動で行う必要があり、.Net Core の内蔵機能で EnableRewind() でリクエスト ストリームを読み込んだ後に再読み込みできるような、一度書き込んだレスポンス ストリームを読み込める .Net Core の組み込み機能を知りません。

あなたの "hacky" アプローチは、おそらく完全に適切です。基本的に、シークできないストリームをシークできるストリームに変換しているのです。 結局のところ Response.Body ストリームは、バッファリングされシークをサポートするストリームと交換されなければなりません。 これを行うためのミドルウェアの別の方法を紹介しますが、あなたのアプローチと非常によく似ていることにお気づきでしょう。 しかし、私は元のストリームをに戻すための追加保護として、最終的にブロックを使用することにしました。 Response.Body に戻し、さらに Position プロパティではなく、ストリームの Seek メソッドよりもストリームのプロパティの方が、構文が少し単純なので、効果はあなたのアプローチと変わりません。

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

        } 
}