web-dev-qa-db-ja.com

.NET Core FilterAttributeでリクエストボディを取得する方法はありますか?

私のリクエストのサンプル

http://localhost:8065/api/note
POST
content-type:application/json
request body: { "id" : "1234", "title" : "test", "status" : "draft"}

そして応答は

{ "msg" : "ok", "code" : 1}

アクション

public async Task<IActionResult> Post([FromBody]NoteModel model)

すべてのリクエストを自動的にログに記録するために、このジョブを実行する属性を作成します。属性は次のようになります:(from Microsoft Docs

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogDebug("[path]" + context.HttpContext.Request.Path);
            _logger.LogDebug("[method]" + context.HttpContext.Request.Method);
            _logger.LogDebug("[body]"); //log request body, expectation: { "id" : "1234", "title" : "test", "status" : "draft"}
            _logger.LogDebug("[statuscode]" + context.HttpContext.Response.StatusCode);
            _logger.LogDebug("[response]"); //log response
        }
    }
}

StreamReaderを使用してリクエストの本文を取得しようとすると、空の文字列のみが取得されます。

StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string text = reader.ReadToEnd();

コントローラーから[fromBody]によってボディが読み取られたため、ストリームを2回読み取ることができないためですか?もしそうなら、どのようにOnActionExecutedメソッドでリクエストボディとレスポンスを取得するべきですか?


更新:

Setのコードをプロジェクトにコピーしましたが、機能しません。これがデバッグgif enter image description here

6
wtf512

この「 ミドルウェアのリクエスト本文をログに記録/読み取るための最良の方法 」スレッドに応じて、以下が機能するはずです。

// using Microsoft.AspNetCore.Http.Internal;

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    ... 

    public void OnActionExecuting(ActionExecutedContext context)
    {
        // read body before MVC action execution
        string bodyData = ReadBodyAsString(context.HttpContext.Request);
    }

    private string ReadBodyAsString(HttpRequest request)
    {
        var initialBody = request.Body; // Workaround

        try
        {
            request.EnableRewind();

            using (StreamReader reader = new StreamReader(request.Body))
            {
                string text = reader.ReadToEnd();
                return text;
            }
        }
        finally
        {
            // Workaround so MVC action will be able to read body as well
            request.Body = initialBody; 
        }

        return string.Empty;
    }
 }

リクエストボディを2回読み取る SO postで説明されている同様のアプローチ


UpdateReadBodyAsStringでの上記のアプローチは、アクションフィルターではなくミドルウェアで使用すると機能します。違いは、アクションフィルターが呼び出されているとき(OnActionExecutingの場合でも)、ボディストリームが既に読み取られ、[FromBody] modelが入力されていることです。

良いneswは、context.ActionArguments["<model_name>"]を使用してアクションフィルターで直接モデルを取得できることです。あなたの場合:

public void OnActionExecuted(ActionExecutedContext context)
{
   var model = context.ActionArguments["model"] as NoteModel;
}
10
Set

コントローラーでIActionResultを使用していて、.NETオブジェクトが必要な場合は、次のようなフィルターを作成できます。

public class SampleFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Result is ObjectResult)
        {
            var objResult = (ObjectResult)context.Result;
        }
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {

    }
}

OnActionExecutedに到達する時点で、ObjectResultタスクはすでに完了しているため、値を抽出するだけで済みます。 objResult.StatusCodeでStatusCodeを取得することもできます。

コントローラでは、Ok(...)を返すと実際にOkObjectResultなどが作成されます。

特にシリアライズされた結果が必要な場合は、セットの答えがより有効です。

0
John