web-dev-qa-db-ja.com

アクションフィルターでWeb APIリクエストコンテンツが空です

リクエストとレスポンスの内容をテキストファイルに記録しようとするLogという属性があります。それをコントローラーの上に置いて、すべてのアクションをカバーします。 LogAttributeでは、コンテンツを文字列(ReadAsStringAsync)として読み取っているので、リクエストの本文を失うことはありません。

public class LogAttribute : ActionFilterAttribute
{
    // ..
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // stuff goes here
        var content = actionContext.Request.Content.ReadAsStringAsync().Result; 
        // content is always empty because request body is cleared
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        // other stuff goes here
        var content = actionContext.Request.Content.ReadAsStringAsync().Result;
        // content is always empty because request body is cleared
    }

    // ..
}

一方、その利点を活用するために、アクションパラメータークラスの前にFromBody属性を配置しました。

[Log]
public class SomethingController
{
    public HttpResponseMessage Foo([FromBody] myModel)
    {
        // something
    }
}

問題は、ActionExecutingまたはActionExecutedのいずれかでコンテンツが常に空であることです。

これは、コード内の順序とは異なり、FromBodyがLog属性の前に実行されるためだと思います。繰り返しますが、アクションパラメータ(ルート処理)に従ってリクエストに最適なアクション/コントローラの一致を見つけるためです。その後、リクエストボディはWebApiでバッファリングされないため、リクエストボディはクリアされます。

FromBody属性とログ属性の実行時の順序を変更する方法があるかどうかを知りたいですか?または問題を解決する他の何か! FromBodyを削除したくなく、Modelなどの代わりにHttpRequestMessageを使用したくないことを述べておきます。

32
Reza Ahmadi

リクエストの本文は巻き戻せないストリームです。一度だけ読み取ることができます。フォーマッタはすでにストリームを読み取り、モデルにデータを入力しています。アクションフィルターで再度ストリームを読み取ることはできません。

あなたは試すことができます:

public class LogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var myModel = actionContext.ActionArguments["myModel"]; 

    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var myModel = actionContext.ActionArguments["myModel"]; 
    }
}

実際、ActionArgumentsは単なる辞書です。ハードコードされたパラメーター名("myModel")。特定の要件のために類似のオブジェクトのクラスで作業する必要がある汎用アクションフィルターを作成する場合、モデルにインターフェイスを実装させることができます=>どの引数が作業する必要があるモデルであるかを認識し、メソッドを呼び出すことができますがインターフェース。

コード例:

public class LogAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))
            {
                 ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable
                 //do something with it. Maybe call model.log
            }
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))
            {
                 ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable
                 //do something with it. Maybe call model.log
            }
        }
    }
30
Khanh TO

このアプローチは私にとってうまくいきました:

using (var stream = new MemoryStream())
{
    var context = (HttpContextBase)Request.Properties["MS_HttpContext"];
    context.Request.InputStream.Seek(0, SeekOrigin.Begin);
    context.Request.InputStream.CopyTo(stream);
    string requestBody = Encoding.UTF8.GetString(stream.ToArray());
}

ロギングまたは例外ケースをトリガーするアクションパラメータオブジェクトのjson表現を私に返しました。

受け入れられた回答として見つかりました ここ

15
Anytoe
public class ContentInterceptorHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            var requestBody = await request.Content.ReadAsStringAsync();
            request.Properties["Content"] = requestBody;
            request.Content = new StringContent(requestBody, Encoding.UTF8, request.Content.Headers.ContentType.MediaType);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

public class LogRequestAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.Request.Properties.TryGetValue("Content", out var body))
            return;

        Console.WriteLine(body);
    }
}

スタートアップに追加

httpConfiguration.MessageHandlers.Add(new ContentInterceptorHandler());

これは私のために働きました:

public override async Task OnActionExecutedAsync(HttpActionExecutedContext context, CancellationToken cancellationToken) {

                var requestLog = context.Request;
                if (requestLog != null)
                {
                    _logger.DebugFormat("Request: {0}", requestLog?.ToString());
                    var requestBody = context.ActionContext?.ActionArguments;
                    if (requestBody != null)
                    {
                        _logger.DebugFormat("Body: {0}", JsonConvert.SerializeObject(requestBody));
                    }
                }                   
    }
0
Filippo A.