È necessario accedere al corpo della richiesta e della risposta di asp.net webapi 2 in un database


103

Sto utilizzando Microsoft Asp.net WebApi2 ospitato su IIS. Vorrei semplicemente registrare il corpo della richiesta (XML o JSON) e il corpo della risposta per ogni post.

Non c'è niente di speciale in questo progetto o nel controllore che elabora il post. Non mi interessa usare framework di registrazione come nLog, elmah, log4net o le funzionalità di tracciamento integrate dell'API web a meno che non sia necessario farlo.

Vorrei semplicemente sapere dove inserire il mio codice di registrazione e come ottenere il JSON o XML effettivo dalla richiesta e risposta in entrata e in uscita.

Il mio metodo di post del controller:

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

}

Stai cercando di registrare la richiesta / risposta per una particolare azione, un set o tutte le tue azioni in un particolare controller?
LB2

Interessato solo alla registrazione di Post. (a) Ora del post (b) corpo di xml o json pubblicato (c) risposta (il contenuto xml o json) insieme al codice di stato
HTTP

Il motivo per cui stavo chiedendo è suggerire se mettere il codice direttamente in azione o una soluzione generica a tutte le azioni. Vedi la mia risposta di seguito.
LB2

Cordiali saluti, ho rimosso asp.net in quanto non riguarda questa domanda
Dalorzo

creare un filer non è un'opzione?
Prerak K

Risposte:


194

Consiglierei di usare un file DelegatingHandler. Quindi non dovrai preoccuparti di alcun codice di registrazione nei tuoi controller.

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

Basta sostituire Trace.WriteLinecon il codice di registrazione e registrare il gestore in WebApiConfigquesto modo:

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

Ecco la documentazione Microsoft completa per i gestori di messaggi .


3
task.Result.Contentritorna System.Net.Http.ObjectContent. C'è un modo per ottenere invece il raw xml / json?
PC.

4
@SoftwareFactor: ContinueWithe Resultsono API pericolose. Sarebbe molto meglio usare awaitinvece, cioè,var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary

9
Questa è una soluzione molto interessante, tuttavia genererà un errore quando la risposta non contiene alcun corpo. Ma è abbastanza facile da controllare e correggere :)
buddybubble

6
La chiamata a await request.Content.ReadAsStringAsync();non restituisce un errore che indica che il flusso di richiesta è già stato letto in determinate circostanze?
Gavin

6
Se il gestore delegante legge il corpo della richiesta, non lo renderebbe non disponibile per l'effettivo gestore del terminale (cioè mvc / webapi)?
LB2

15

Esistono più approcci per gestire genericamente la registrazione di richieste / risposte per ogni chiamata al metodo WebAPI:

  1. ActionFilterAttribute: Si può scrivere personalizzato ActionFilterAttributee decorare i metodi di controller / azione per abilitare la registrazione.

    Contro: è necessario decorare ogni controller / metodo (è comunque possibile farlo sul controller di base, ma non risolve i problemi di taglio incrociato.

  2. Esegui l'override BaseControllere gestisci la registrazione lì.

    Contro: Ci aspettiamo / costringiamo i controller a ereditare da un controller di base personalizzato.

  3. Utilizzando DelegatingHandler.

    Vantaggio: non stiamo toccando controller / metodo qui con questo approccio. Il gestore di delega si trova in isolamento e gestisce con grazia la registrazione di richieste / risposte.

Per un articolo più approfondito, fare riferimento a questo http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi .


È possibile assegnare qualsiasi actionfilter come segue: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Web API configuration and services config.Filters.Add (new MyFilter ()) // Web API routes config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (nome: "DefaultApi", routeTemplate: "api / {controller} / {id}", defaults: new {id = RouteParameter.Optional}); }}
Mika Karjunen

11

Una delle opzioni che hai è usare la creazione di un filtro di azione e decorare il tuo WebApiController / ApiMethod con esso.

Attributo di filtro

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

Controller WebApi

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

o

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

Spero che questo ti aiuti.


Mi piace questo approccio ma per ottenere la risposta devo invece sostituire OnActionExecuted. Il problema è che la richiesta a quel punto è già stata convertita nel mio POCO invece di essere xml o json. qualche idea?
user2315985

Inizialmente intendevo, registra i dati in OnActionExecuting e poi lascia semplicemente che il post faccia il suo lavoro. Quello che ho capito dalla tua domanda era che vuoi solo registrare i dati per ogni post che è stato fatto.
Prerak K

3
Voglio registrare sia la richiesta che i dati di risposta ogni volta che qualcuno pubblica.
user2315985

2
è possibile utilizzare OnActionExecuted e provare "(actionExecutedContext.ActionContext.Response.Content as ObjectContent) .Value.ToString ()" per ottenere la risposta e registrarla.
Prerak K

Come ottengo la richiesta da OnActionExecuted?
user2315985

3

Ottenere l'accesso al messaggio di richiesta è facile. La tua classe base,ApiController contiene una .Requestproprietà che, come suggerisce il nome, contiene la richiesta in forma analizzata. Basta esaminarlo per qualsiasi cosa tu stia cercando di registrare e passarlo alla tua struttura di registrazione, qualunque essa sia. Questo codice puoi inserire all'inizio della tua azione, se devi farlo solo per uno o per una manciata.

Se hai bisogno di farlo su tutte le azioni (tutto significa più di una manciata gestibile), allora quello che puoi fare è sovrascrivere il .ExecuteAsyncmetodo per catturare ogni chiamata di azione per il tuo controller.

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

Lo sto facendo e non l'ho ancora valutato, solo la mia intuizione mi dice che può essere molto lento?
Marcus

Perché pensi che sarebbe lento? ExecuteAsyncè ciò che viene chiamato dal framework e l'implementazione della classe controller di base è ciò che effettivamente esegue l'azione. Questa è solo una chiamata alla registrazione come parte dell'esecuzione già in corso. L'unica penalità qui è il momento di eseguire la registrazione effettiva.
LB2

No, voglio dire, "molto lento" come nel registrare ogni richiesta.
Marcus

2
Bene, questa è una questione di requisiti, e questo è il requisito dichiarato da OP. È una questione di volume che il sito gestisce, le prestazioni della struttura di registrazione, ecc. Questo va oltre il post degli OP.
LB2

0

Questo sembra essere un thread piuttosto vecchio, ma sto condividendo un'altra soluzione.

Puoi aggiungere questo metodo nel tuo file global.asax che verrà attivato ogni volta che termina la richiesta 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

Questo è un argomento davvero vecchio ma ho passato molto tempo (ricerca in Internet) per fare queste cose, quindi pubblicherò qui la mia soluzione.

Concetto

  1. Override ExecuteAsync del metodo APicontroller per il monitoraggio della richiesta in entrata, nella mia soluzione creo Base_ApiController come genitore dei controller API del mio progetto.
  2. Usa System.Web.Http.Filters.ActionFilterAttribute per tenere traccia della risposta in uscita del controller api
  3. *** (aggiuntivo) *** Utilizzare System.Web.Http.Filters.ExceptionFilterAttribute per registrare quando si verifica un'eccezione.

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

        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.