Sie müssen den Anforderungs- und Antworttext von asp.net webapi 2 in einer Datenbank protokollieren


103

Ich verwende Microsoft Asp.net WebApi2, das auf IIS gehostet wird. Ich möchte ganz einfach den Anfragetext (XML oder JSON) und den Antworttext für jeden Beitrag protokollieren.

Es gibt nichts Besonderes an diesem Projekt oder dem Controller, der den Beitrag bearbeitet. Ich bin nicht daran interessiert, Protokollierungsframeworks wie nLog, elmah, log4net oder die integrierten Ablaufverfolgungsfunktionen der Web-API zu verwenden, es sei denn, dies ist erforderlich.

Ich möchte einfach nur wissen, wo ich meinen Protokollierungscode ablegen soll und wie ich den tatsächlichen JSON oder XML aus der eingehenden und ausgehenden Anforderung und Antwort erhalten kann.

Meine Controller-Post-Methode:

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

}

Möchten Sie die Anforderung / Antwort für eine bestimmte Aktion, einen Satz oder alle Ihre Aktionen in einem bestimmten Controller protokollieren?
LB2

Nur an Logging Post interessiert. (a) Zeitpunkt der Veröffentlichung (b) Textkörper von XML oder JSON veröffentlicht (c) Antwort (der XML- oder JSON-Inhalt) zusammen mit dem
HTTP-Statuscode

Der Grund, den ich gefragt habe, ist, vorzuschlagen, ob Code direkt in die Tat umgesetzt werden soll, oder eine generische Lösung für alle Aktionen. Siehe meine Antwort unten.
LB2

Zu Ihrer Information Ich habe asp.net entfernt, da es diese Frage nicht
betrifft

Ist das Erstellen eines Filers keine Option?
Prerak K

Antworten:


192

Ich würde empfehlen, eine DelegatingHandler. Dann müssen Sie sich keine Gedanken mehr über Protokollierungscode in Ihren Controllern machen.

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

Ersetzen Trace.WriteLineSie einfach Ihren Protokollierungscode und registrieren Sie den Handler WebApiConfigwie folgt:

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

Hier finden Sie die vollständige Microsoft-Dokumentation für Message Handler .


3
task.Result.Contentkehrt zurück System.Net.Http.ObjectContent. Gibt es eine Möglichkeit, stattdessen das rohe xml / json zu erhalten?
PC.

4
@ SoftwareFactor: ContinueWithund Resultsind gefährliche APIs. Es wäre weitaus besser, awaitstattdessen zu verwenden , dhvar result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary

9
Dies ist eine sehr coole Lösung, die jedoch einen Fehler auslöst, wenn die Antwort keinen Text enthält. Aber das ist einfach genug zu überprüfen und zu beheben :)
Buddybubble

6
Führt der Aufruf await request.Content.ReadAsStringAsync();nicht zu einem Fehler, der besagt, dass der Anforderungsdatenstrom unter bestimmten Umständen bereits gelesen wurde?
Gavin

6
Wenn der delegierende Handler den Hauptteil der Anforderung liest, würde er dann nicht für den tatsächlichen Terminal-Handler (dh mvc / webapi) nicht verfügbar sein?
LB2

15

Es gibt mehrere Ansätze, um die Anforderungs- / Antwortprotokollierung für jeden WebAPI-Methodenaufruf generisch zu verarbeiten:

  1. ActionFilterAttribute: Man kann benutzerdefiniert schreiben ActionFilterAttribute und die Controller- / Aktionsmethoden dekorieren, um die Protokollierung zu aktivieren.

    Con: Sie müssen jeden Controller / jede Methode dekorieren (Sie können dies immer noch auf dem Basis-Controller tun, aber es werden immer noch keine Querschnittsthemen angesprochen.

  2. Überschreiben BaseController und behandeln Sie die Protokollierung dort.

    Con: Wir erwarten / zwingen die Controller, von einem benutzerdefinierten Basis-Controller zu erben.

  3. Verwenden von DelegatingHandler .

    Vorteil: Wir berühren hier nicht die Steuerung / Methode mit diesem Ansatz. Der delegierende Handler befindet sich isoliert und verwaltet die Anforderungs- / Antwortprotokollierung ordnungsgemäß.

Weitere Informationen finden Sie unter http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi .


Sie können jeden Aktionsfilter wie folgt zuweisen: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Konfiguration der Web-API und Dienste config.Filters.Add (new MyFilter ()) // Routen der Web-API config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (Name: "DefaultApi", routeTemplate: "api / {controller} / {id}", Standard: new {id = RouteParameter.Optional}); }}
Mika Karjunen

11

Eine der Optionen, die Sie haben, besteht darin, einen Aktionsfilter zu erstellen und Ihren WebApiController / ApiMethod damit zu dekorieren.

Filterattribut

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-Controller

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

oder

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

Hoffe das hilft.


Ich mag diesen Ansatz, aber um die Antwort zu erhalten, muss ich stattdessen OnActionExecuted überschreiben. Das Problem ist, dass die Anforderung zu diesem Zeitpunkt bereits in mein POCO konvertiert wurde, anstatt XML oder JSON zu sein. Irgendwelche Gedanken?
user2315985

Ursprünglich meinte ich, Daten in OnActionExecuting zu protokollieren und dann einfach den Beitrag seine Arbeit machen zu lassen. Was ich aus Ihrer Frage verstanden habe, war, dass Sie nur die Daten für jeden Beitrag protokollieren möchten, der fertig ist.
Prerak K

3
Ich möchte jedes Mal, wenn jemand etwas veröffentlicht, sowohl die Anforderungs- als auch die Antwortdaten protokollieren.
user2315985

2
Sie können OnActionExecuted verwenden und versuchen, "(actionExecutedContext.ActionContext.Response.Content als ObjectContent) .Value.ToString ()" abzurufen und zu protokollieren.
Prerak K

Wie erhalte ich die Anfrage in OnActionExecuted?
user2315985

3

Der Zugriff auf die Anforderungsnachricht ist einfach. Ihre BasisklasseApiController enthält eine .RequestEigenschaft , die, wie der Name schon sagt, die Anforderung in analysierter Form enthält. Sie überprüfen es einfach auf das, was Sie protokollieren möchten, und geben es an Ihre Protokollierungsfunktion weiter, je nachdem, um was es sich handelt. Diesen Code können Sie zu Beginn Ihrer Aktion einfügen, wenn Sie ihn nur für eine oder eine Handvoll ausführen müssen.

Wenn Sie dies für alle Aktionen tun müssen (alles bedeutet mehr als eine überschaubare Handvoll), können Sie die .ExecuteAsyncMethode überschreiben , um jeden Aktionsaufruf für Ihren Controller zu erfassen.

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

Ich mache das und habe es noch nicht gemessen. Nur meine Intuition sagt mir, dass dies sehr langsam sein kann.
Marcus

Warum denkst du, würde es langsam sein? ExecuteAsyncwird vom Framework aufgerufen, und die Implementierung der Basis-Controller-Klasse bewirkt, dass die Aktion tatsächlich ausgeführt wird. Dies ist nur ein Aufruf Ihrer Protokollierung als Teil der bereits laufenden Ausführung. Die einzige Strafe hier ist die Zeit für die eigentliche Protokollierung.
LB2

Nein, ich meine, "sehr langsam" wie bei der Protokollierung jeder Anfrage.
Marcus

2
Nun, das ist eine Frage der Anforderungen, und das ist die Anforderung, die von OP angegeben wird. Es ist eine Frage des Volumens, das die Site verarbeitet, der Leistung der Protokollierungsfunktion usw. Das geht über den Beitrag des OP hinaus.
LB2

0

Dies scheint ein ziemlich alter Thread zu sein, aber wir teilen eine andere Lösung.

Sie können diese Methode in Ihre global.asax-Datei einfügen, die jedes Mal nach dem Ende der HTTP-Anforderung ausgelöst wird.

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

Dies ist ein wirklich altes Thema, aber ich habe viel Zeit damit verbracht (im Internet zu suchen), um diese Sache zu tun, also werde ich einfach meine Lösung hier posten.

Konzept

  1. Override ExecuteAsync der APicontroller-Methode zum Verfolgen eingehender Anforderungen. In meiner Lösung erstelle ich Base_ApiController als übergeordnetes Element der API-Controller meines Projekts.
  2. Verwenden Sie System.Web.Http.Filters.ActionFilterAttribute, um die ausgehende Antwort des API-Controllers zu verfolgen
  3. *** (Zusätzlich) *** Verwenden Sie System.Web.Http.Filters.ExceptionFilterAttribute, um zu protokollieren, wenn eine Ausnahme auftritt.

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

        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.