asp.net webapi 2リクエストとレスポンスの本文をデータベースに記録する必要がある


103

IISでホストされているMicrosoft Asp.net WebApi2を使用しています。非常に単純に、各投稿の要求本文(XMLまたはJSON)と応答本文をログに記録します。

このプロジェクトや投稿を処理するコントローラーについて特別なことは何もありません。必要がない限り、nLog、elmah、log4netなどのロギングフレームワーク、またはWeb APIの組み込みのトレース機能を使用することに興味はありません。

ロギングコードをどこに配置するか、および実際のJSONまたはXMLを着信および発信の要求と応答から取得する方法を知りたいだけです。

私のコントローラーの投稿方法:

public HttpResponseMessage Post([FromBody])Employee employee)
{
   if (ModelState.IsValid)
   {
      // insert employee into to the database
   }

}

特定のコントローラ、特定のアクション、セット、またはすべてのアクションのリクエスト/レスポンスをログに記録することを考えていますか?
LB2 2014年

Postのロギングのみに関心があります。(a)投稿時刻(b)投稿されたxmlまたはjsonの本文(c)応答(xmlまたはjsonコンテンツ)とHttpステータスコード
user2315985

私が尋ねていた理由は、コードを直接実行するか、またはすべてのアクションの一般的な解決策を提案するかです。以下の私の答えを参照してください。
LB2 2014年

参考までに、asp.netはこの質問に関係がないため削除しました
Dalorzo、

オプションではなくファイラーを作成していますか?
Prera​​k K 2014

回答:


192

の使用をお勧めしますDelegatingHandler。そうすれば、コントローラーのロギングコードについて心配する必要はありません。

public class LogRequestAndResponseHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            // log request body
            string requestBody = await request.Content.ReadAsStringAsync();
            Trace.WriteLine(requestBody);
        }
        // let other handlers process the request
        var result = await base.SendAsync(request, cancellationToken);

        if (result.Content != null)
        {
            // once response body is ready, log it
            var responseBody = await result.Content.ReadAsStringAsync();
            Trace.WriteLine(responseBody);
        }

        return result;
    }
}

Trace.WriteLineロギングコードに置き換えて、次のWebApiConfigようにハンドラを登録します。

config.MessageHandlers.Add(new LogRequestAndResponseHandler());

これは、メッセージハンドラに関するMicrosoftの完全なドキュメントです。


3
task.Result.Contentを返しますSystem.Net.Http.ObjectContent。代わりに生のxml / jsonを取得する方法はありますか?
PC。

4
@SoftwareFactor:ContinueWithResult危険なAPIです。await代わりに使用する方がはるかに優れています。つまり、var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary 2015年

9
これは非常に優れたソリューションですが、応答に本文が含まれていない場合はエラーがスローされます。しかし、それはチェックして修正するのに十分簡単です:)
buddybubble

6
呼び出しawait request.Content.ReadAsStringAsync();は、リクエストストリームが特定の状況ですでに読み取られているというエラーを発生させませんか?
Gavin

6
委任ハンドラーが要求の本文を読み取る場合、実際の端末ハンドラー(つまり、mvc / webapi)で使用できないようにしないのですか?
LB2 2017

15

すべてのWebAPIメソッド呼び出しの要求/応答ロギングを一般的に処理するには、複数のアプローチがあります。

  1. ActionFilterAttribute:カスタムActionFilterAttributeを記述して、コントローラー/アクションメソッドを装飾し、ロギングを有効にできます。

    短所:すべてのコントローラー/メソッドを装飾する必要があります(まだベースコントローラーで実行できますが、それでも横断的な懸念には対応していません。

  2. BaseControllerそこでログをオーバーライドして処理します。

    欠点:コントローラーがカスタムベースコントローラーから継承することを期待/強制しています。

  3. を使用しDelegatingHandlerます。

    利点:このアプローチでは、コントローラー/メソッドには触れません。委任ハンドラーは分離して配置され、要求/応答ログを適切に処理します。

詳細な記事については、こちらのhttp://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapiを参照してください


次のように、任意のactionfilterを割り当てることができます。 (); config.Routes.MapHttpRoute(name: "DefaultApi"、routeTemplate: "api / {controller} / {id}"、defaults:new {id = RouteParameter.Optional}); }}
Mika Karjunen

11

オプションの1つは、アクションフィルターを作成し、それでWebApiController / ApiMethodを装飾することです。

フィルター属性

public class MyFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.Request.Method == HttpMethod.Post)
            {
                var postData = actionContext.ActionArguments;
                //do logging here
            }
        }
    }

WebApiコントローラー

[MyFilterAttribute]
public class ValuesController : ApiController{..}

または

[MyFilterAttribute]
public void Post([FromBody]string value){..}

お役に立てれば。


私はこのアプローチが好きですが、応答を取得するには、代わりにOnActionExecutedをオーバーライドする必要があります。問題は、その時点でのリクエストがxmlまたはjsonではなく、すでにPOCOに変換されていることです。何かご意見は?
user2315985 2014年

もともと私は、OnActionExecutingのログデータと、単純に投稿にその仕事を任せることを意味していました。あなたの質問から私が理解したのは、行われた各投稿のデータをログに記録したいということでした。
Prera​​k K 2014

3
誰かが投稿するたびに、リクエストとレスポンスの両方のデータをログに記録したい。
user2315985 2014年

2
OnActionExecutedを使用して、「(actionExecutedContext.ActionContext.Response.Content as ObjectContent).Value.ToString()」を試行して応答を取得し、ログに記録することができます。
Prera​​k K 2014

OnActionExecuted内からリクエストを取得するにはどうすればよいですか?
user2315985 2014年

3

リクエストメッセージへのアクセスは簡単です。あなたの基底クラスは、ApiController含まれている.Requestプロパティを名の通り、、、解析された形での要求が含まれています。記録しようとしているものを調べて、ロギング機能に渡すだけです。このコードは、1つまたは少数の操作で実行する必要がある場合に、アクションの最初に配置できます。

すべてのアクションでそれを実行する必要がある場合(すべては管理可能な一握り以上のものを意味します)、.ExecuteAsyncコントローラーのすべてのアクション呼び出しをキャプチャーするメソッドをオーバーライドすることができます。

public override Task<HttpResponseMessage> ExecuteAsync(
    HttpControllerContext controllerContext,
    CancellationToken cancellationToken
)
{
    // Do logging here using controllerContext.Request
    return base.ExecuteAsync(controllerContext, cancellationToken);
}

私はこれを行っていますが、まだベンチマークを行っていませんが、直感でこれが非常に遅くなる可能性があることがわかりますか?
Marcus

なぜ遅いと思いますか? ExecuteAsyncフレームワークによって呼び出されるものであり、基本コントローラクラスの実装は、実際にアクションを実行するものです。これは、すでに発生している実行の一部としてロギングを呼び出すだけです。ここでのペナルティは、実際のロギングを行うときだけです。
LB2 2017

いいえ、つまり、すべてのリクエストをログに記録する場合のように「非常に遅い」。
Marcus

2
まあ、それは要件の問題であり、それはOPによって述べられた要件です。それは、サイトが処理するボリューム、ロギング機能のパフォーマンスなどの問題です。それは、OPポストを超えています。
LB2 2017

0

これはかなり古いスレッドのようですが、別のソリューションを共有する価値があります。

このメソッドをglobal.asaxファイルに追加すると、HTTPリクエストが終了するたびにトリガーされます。

void Application_EndRequest(Object Sender, EventArgs e)
    {
        var request = (Sender as HttpApplication).Request;
        var response = (Sender as HttpApplication).Response;

        if (request.HttpMethod == "POST" || request.HttpMethod == "PUT")
        {


            byte[] bytes = request.BinaryRead(request.TotalBytes);
            string body = Encoding.UTF7.GetString(bytes);
            if (!String.IsNullOrEmpty(body))
            {


                // Do your logic here (Save in DB, Log in IIS etc.)
            }
        }
    }

0

これは本当に古いトピックですが、私はこれらのことをするために多くの時間を費やしました(インターネットを検索します)。

概念

  1. インバウンド要求を追跡するためにAPicontrollerメソッドのExecuteAsyncをオーバーライドします。私のソリューションでは、プロジェクトのAPIコントローラーの親としてBase_ApiControllerを作成します。
  2. System.Web.Http.Filters.ActionFilterAttributeを使用してAPIコントローラのアウトバウンド応答を追跡する
  3. ***(追加)***例外が発生したときにSystem.Web.Http.Filters.ExceptionFilterAttributeを使用してログに記録します。

1. MyController.cs

    [APIExceptionFilter]  // use 3.
    [APIActionFilter]     // use 2.
    public class Base_APIController : ApiController
    {
        public   bool  IsLogInbound
        {
            get
            { return   ConfigurationManager.AppSettings["LogInboundRequest"] =="Y"? true:false ;     }
        }
        /// <summary>
        /// for logging exception
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override Task<HttpResponseMessage> ExecuteAsync(
         HttpControllerContext controllerContext,
         CancellationToken cancellationToken
         )
        {
            // Do logging here using controllerContext.Request
            // I don't know why calling the code below make content not null Kanit P.
            var content = controllerContext.Request.Content.ReadAsStringAsync().Result.ToString(); // keep request json content
             // Do your own logging!
            if (IsLogInbound)
            {
                try
                {
                    ErrLog.Insert(ErrLog.type.InboundRequest, controllerContext.Request,
                         controllerContext.Request.RequestUri.AbsoluteUri
                         , content);
                }
                catch (Exception e) { }
            }

            // will not log err when go to wrong controller's action (error here but not go to APIExceptionFilter)
            var t = base.ExecuteAsync(controllerContext, cancellationToken);
            if (!t.Result.IsSuccessStatusCode)
            { 
            }
            return t;

        }

2. APIActionFilter.cs

    public class APIActionFilter : System.Web.Http.Filters.ActionFilterAttribute
    {
        public bool LogOutboundRequest
        {
            get
            { return ConfigurationManager.AppSettings["LogInboundRequest"] == "Y" ? true : false; }
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            try {

                var returndata = actionExecutedContext.Response.Content.ReadAsStringAsync().Result.ToString(); 
             //keep Json response content
             // Do your own logging!
                if (LogOutboundRequest)
                {
                    ErrLog.Insert(ErrLog.type.OutboundResponse, actionExecutedContext.Response.Headers,
                       actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
                      + "/"
                      + actionExecutedContext.ActionContext.ActionDescriptor.ActionName
                      , returndata );
                }
            } catch (Exception e) {

            }
     

        } 
    }
}

3. APIExceptionFilter.cs

    public class APIExceptionFilter : ExceptionFilterAttribute
    {
    public bool IsLogErr
    {
        get
        { return ConfigurationManager.AppSettings["LogExceptionRequest"] == "Y" ? true : false; }
    }


    public override void OnException(HttpActionExecutedContext context)
    {
        try
        { 
            //Do your own logging!
            if (IsLogErr)
            {
                ErrLog.Insert(ErrLog.type.APIFilterException, context.Request,
                    context.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
                    + "/"
                    + context.ActionContext.ActionDescriptor.ActionName
                    , context.Exception.ToString() + context.Exception.StackTrace);
            }
        }catch(Exception e){

        }

        if (context.Exception is NotImplementedException)
        {
            context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
        }
        else {
            context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);

        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.