Należy zarejestrować treść żądania i odpowiedzi asp.net webapi 2 do bazy danych

104

Używam Microsoft Asp.net WebApi2 hostowanego przez IIS. Chciałbym po prostu zarejestrować treść żądania (XML lub JSON) i treść odpowiedzi dla każdego posta.

Nie ma nic specjalnego w tym projekcie ani w kontrolerze przetwarzającym post. Nie interesuje mnie używanie struktur rejestrowania, takich jak nLog, elmah, log4net lub wbudowanych funkcji śledzenia internetowego interfejsu API, chyba że jest to konieczne.

Chcę po prostu wiedzieć, gdzie umieścić mój kod logowania i jak uzyskać rzeczywisty kod JSON lub XML z przychodzących i wychodzących żądań i odpowiedzi.

Metoda mojego posta kontrolera:

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

}
user2315985
źródło
Czy chcesz rejestrować żądanie / odpowiedź dla określonej akcji, zestawu lub wszystkich akcji w określonym kontrolerze?
LB2
Zainteresowany tylko logowaniem Post. (a) Godzina wysłania (b) treść opublikowanej treści xml lub json (c) odpowiedź (treść xml lub json) wraz z kodem statusu HTTP
user2315985
Powodem, o który prosiłem, jest zasugerowanie, czy wprowadzić kod bezpośrednio do działania, czy też ogólne rozwiązanie wszystkich działań. Zobacz moją odpowiedź poniżej.
LB2
FYI
Usunąłem
czy tworzenie filtru nie wchodzi w grę?
Prerak K

Odpowiedzi:

195

Polecam użycie DelegatingHandler. Wtedy nie będziesz musiał martwić się o kod logowania w kontrolerach.

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

Po prostu zastąp Trace.WriteLinekodem logowania i zarejestruj program obsługi w WebApiConfignastępujący sposób:

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

Oto pełna dokumentacja firmy Microsoft dotycząca programów obsługi wiadomości .

SoftwareFactor
źródło
3
task.Result.Contentzwraca System.Net.Http.ObjectContent. Czy istnieje sposób na uzyskanie zamiast tego surowego pliku xml / json?
PC.
4
@SoftwareFactor: ContinueWithi Resultsą niebezpiecznymi interfejsami API. Byłoby znacznie lepiej użyć awaitzamiast tego, np.var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary
9
To bardzo fajne rozwiązanie, jednak wyrzuci błąd, gdy odpowiedź nie będzie zawierała treści. Ale to dość łatwe do sprawdzenia i naprawienia :)
buddybubble,
6
Czy wywołanie await request.Content.ReadAsStringAsync();nie powoduje błędu informującego, że strumień żądania został już odczytany w pewnych okolicznościach?
Gavin,
6
Jeśli procedura obsługi delegująca odczytuje treść żądania, czy nie spowoduje to jej niedostępności dla faktycznej obsługi terminala (tj. Mvc / webapi)?
LB2
15

Istnieje wiele podejść do ogólnej obsługi rejestrowania żądań / odpowiedzi dla każdego wywołania metody WebAPI:

  1. ActionFilterAttribute: Można napisać własne ActionFilterAttributei ozdobić kontroler / metody akcji, aby umożliwić logowanie.

    Wada: Musisz udekorować każdy kontroler / metodę (nadal możesz to zrobić na kontrolerze podstawowym, ale nadal nie rozwiązuje to problemów związanych z cięciem krzyżowym.

  2. Zastąp BaseControlleri obsłuż rejestrowanie w tym miejscu.

    Wada: Spodziewamy się / zmuszamy kontrolery do dziedziczenia z niestandardowego kontrolera podstawowego.

  3. Korzystanie DelegatingHandler.

    Zaleta: tym podejściem nie dotykamy kontrolera / metody. Delegujący program obsługi siedzi w izolacji i wdzięcznie obsługuje rejestrowanie żądań / odpowiedzi.

Aby uzyskać bardziej szczegółowy artykuł, zapoznaj się z tym http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi .

Venkatesh Muniyandi
źródło
Możesz przypisać dowolny filtr akcji w następujący sposób: publiczna statyczna klasa WebApiConfig {public static void Register (HttpConfiguration config) {// Konfiguracja i usługi interfejsu API sieci Web config.Filters.Add (new MyFilter ()) // Trasy interfejsu API sieci Web config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (nazwa: "DefaultApi", routeTemplate: "api / {controller} / {id}", defaults: new {id = RouteParameter.Optional}); }}
Mika Karjunen
11

Jedną z dostępnych opcji jest tworzenie filtru akcji i dekorowanie nim WebApiController / ApiMethod.

Filtr atrybutu

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

Kontroler WebApi

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

lub

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

Mam nadzieję że to pomoże.

Prerak K
źródło
Podoba mi się to podejście, ale aby uzyskać odpowiedź, muszę zamiast tego zastąpić OnActionExecuted. Problem polega na tym, że żądanie w tym momencie zostało już przekonwertowane na mój POCO zamiast być plikiem XML lub JSON. jakieś pomysły?
user2315985
Pierwotnie miałem na myśli, loguj dane w OnActionExecuting, a następnie po prostu pozwól, aby post wykonał swoją pracę. Z twojego pytania zrozumiałem, że po prostu chcesz rejestrować dane dla każdego wykonanego postu.
Prerak K
3
Chcę rejestrować żądanie i dane odpowiedzi za każdym razem, gdy ktoś publikuje.
user2315985
2
możesz użyć OnActionExecuted i spróbować "(actionExecutedContext.ActionContext.Response.Content jako ObjectContent) .Value.ToString ()", aby uzyskać odpowiedź i zarejestrować ją.
Prerak K
Jak uzyskać żądanie z OnActionExecuted?
user2315985
3

Uzyskanie dostępu do wiadomości z żądaniem jest łatwe. Twoja klasa bazowaApiController zawiera .Requestwłaściwość , która, jak sugeruje nazwa, zawiera żądanie w formie przeanalizowanej. Po prostu sprawdzasz go pod kątem tego, co chcesz zarejestrować, i przekazujesz go do swojego centrum logowania, cokolwiek to jest. Ten kod możesz umieścić na początku swojej akcji, jeśli chcesz to zrobić tylko dla jednej lub kilku osób.

Jeśli musisz to zrobić dla wszystkich akcji (wszystko to znaczy więcej niż garstka, którą można zarządzać), to możesz zrobić .ExecuteAsyncmetodę nadpisania , aby przechwycić każde wywołanie akcji dla twojego kontrolera.

public override Task<HttpResponseMessage> ExecuteAsync(
    HttpControllerContext controllerContext,
    CancellationToken cancellationToken
)
{
    // Do logging here using controllerContext.Request
    return base.ExecuteAsync(controllerContext, cancellationToken);
}
LB2
źródło
Robię to i jeszcze tego nie testowałem, tylko moja intuicja podpowiada mi, że może to być bardzo powolne?
Marcus,
Jak myślisz, dlaczego byłoby to powolne? ExecuteAsyncjest tym, co jest wywoływane przez framework, a implementacja klasy kontrolera podstawowego jest tym, co faktycznie powoduje wykonanie akcji. To po prostu wywołanie logowania w ramach już trwającej realizacji. Jedyną karą jest czas na wykonanie faktycznego logowania.
LB2
Nie, mam na myśli „bardzo powolne”, jak rejestrowanie każdego żądania.
Marcus,
2
Cóż, to kwestia wymagań i to jest wymaganie określone przez OP. Jest to kwestia ilości obsługiwanych przez witrynę, wydajności narzędzia do rejestrowania itp. To nie jest tylko post PO.
LB2
0

Wydaje się, że to dość stary wątek, ale warto udostępnić inne rozwiązanie.

Możesz dodać tę metodę w swoim pliku global.asax, która będzie uruchamiana po zakończeniu żądania 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.)
            }
        }
    }
EMC
źródło
0

To naprawdę stary temat, ale spędziłem dużo czasu (przeszukując internet), aby to zrobić, więc po prostu opublikuję tutaj swoje rozwiązanie.

Pojęcie

  1. Zastąp ExecuteAsync metody APicontroller do śledzenia żądań przychodzących, w moim rozwiązaniu tworzę Base_ApiController jako rodzica kontrolerów API mojego projektu.
  2. Użyj System.Web.Http.Filters.ActionFilterAttribute do śledzenia odpowiedzi wychodzącej kontrolera API
  3. *** (Dodatkowe) *** Użyj System.Web.Http.Filters.ExceptionFilterAttribute, aby zarejestrować, gdy wystąpi wyjątek.

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

        }
    }
}
user3682728
źródło