Asp.net webapi 2 isteğini ve yanıt gövdesini bir veritabanına kaydetmeniz gerekiyor


104

IIS üzerinde barındırılan Microsoft Asp.net WebApi2 kullanıyorum. Çok basit bir şekilde istek gövdesini (XML veya JSON) ve her gönderi için yanıt gövdesini günlüğe kaydetmek istiyorum.

Bu proje veya gönderiyi işleyen denetleyici hakkında özel bir şey yok. Gerekli olmadıkça nLog, elmah, log4net gibi günlük çerçevelerini veya web API'sinin yerleşik izleme özelliklerini kullanmakla ilgilenmiyorum.

Sadece günlük kodumu nereye koyacağımı ve gelen ve giden istek ve yanıttan gerçek JSON veya XML'i nasıl alacağımı bilmek istiyorum.

Denetleyici gönderi yöntemim:

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

}

Belirli bir eylem, bir set veya tüm eylemlerinizi belirli bir denetleyicide günlüğe kaydetmek mi istiyorsunuz?
LB2

Sadece gönderi kaydı yapmakla ilgileniyorum. (a) Gönderme Zamanı (b) Http Durum Kodu ile birlikte gönderilen xml veya json gövdesi (c) yanıtı (xml veya json içeriği)
user2315985

Sormamın nedeni, kodu doğrudan eyleme mi yoksa tüm eylemlere genel çözüm mü koyacağımı önermekti. Aşağıdaki cevabıma bakın.
LB2

Bilginize
asp.net'i

dosya oluşturma bir seçenek değil mi?
Prerak K

Yanıtlar:


195

Bir DelegatingHandler. O zaman denetleyicilerinizdeki herhangi bir kayıt kodu hakkında endişelenmenize gerek kalmaz.

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.WriteLineGünlük kodunuzla değiştirin ve işleyiciyi şu şekilde kaydedin WebApiConfig:

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

İleti İşleyicileri için eksiksiz Microsoft belgeleri burada .


3
task.Result.Contentdöner System.Net.Http.ObjectContent. Bunun yerine ham xml / json'u elde etmenin bir yolu var mı?
PC.

4
@SoftwareFactor: ContinueWithve Resulttehlikeli API'lerdir. Onun awaityerine kullanmak çok daha iyi olur , yanivar result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary

9
Bu çok havalı bir çözüm, ancak yanıt gövde içermediğinde hata verecektir. Ama bu kontrol etmek ve düzeltmek için yeterince kolay :)
buddybubble

6
Çağrı await request.Content.ReadAsStringAsync();, istek akışının belirli koşullarda zaten okunduğunu söyleyen bir hatayla sonuçlanmıyor mu?
Gavin

6
Temsilci işleyici, isteğin gövdesini okursa, onu gerçek uçbirim işleyicisi (yani mvc / webapi) için kullanılamaz hale getirmez mi?
LB2

15

Her WebAPI yöntemi çağrısı için İstek / Yanıt günlüğünü genel olarak işlemek için birden çok yaklaşım vardır:

  1. ActionFilterAttribute: ActionFilterAttributeGünlük kaydını etkinleştirmek için denetleyici / eylem yöntemlerini özel olarak yazabilir ve dekore edebilir .

    Eksileri: Her denetleyiciyi / yöntemi dekore etmeniz gerekiyor (yine de bunu temel denetleyicide yapabilirsiniz, ancak yine de kesişen endişeleri gidermiyor.

  2. BaseControllerOrada günlük kaydını geçersiz kılın ve işleyin.

    Eksileri: Denetleyicilerin özel bir temel denetleyiciden miras almasını bekliyoruz / zorluyoruz.

  3. Kullanarak DelegatingHandler.

    Avantaj: Burada bu yaklaşımla denetleyiciye / yönteme dokunmuyoruz. Temsilci işleyici izole bir şekilde oturur ve istek / yanıt günlük kaydını incelikle işler.

Daha ayrıntılı makale için bu http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi'ye bakın .


Herhangi bir actionfilter'ı aşağıdaki gibi atayabilirsiniz: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Web API configuration and services config.Filters.Add (new MyFilter ()) // Web API route config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (ad: "DefaultApi", routeTemplate: "api / {controller} / {id}", varsayılanlar: yeni {id = RouteParameter.Optional}); }}
Mika Karjunen

11

Sahip olduğunuz seçeneklerden biri, bir eylem filtresi oluşturmak ve WebApiController / ApiMethod'unuzu onunla süslemektir.

Filtreleme Özelliği

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 denetleyicisi

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

veya

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

Bu yardımcı olur umarım.


Bu yaklaşımı seviyorum ama yanıtı almak için bunun yerine OnActionExecuted'ı geçersiz kılmam gerekiyor. Sorun, o noktadaki isteğin xml veya json olmak yerine zaten benim POCO'ma dönüştürülmüş olmasıdır. Düşüncesi olan var mı?
user2315985

Başlangıçta, OnActionExecuting'deki verileri günlüğe kaydetmeyi ve ardından gönderinin işini yapmasına izin vermeyi kastettim. Sorunuzdan anladığım kadarıyla, yaptığınız her gönderi için verileri günlüğe kaydetmek istiyorsunuz.
Prerak K

3
Birisi her gönderi yaptığında hem isteği hem de yanıt verilerini kaydetmek istiyorum.
user2315985

2
Yanıtı almak ve günlüğe kaydetmek için OnActionExecuted'ı kullanabilir ve "(actionExecutedContext.ActionContext.Response.Content as ObjectContent) .Value.ToString ()" deneyebilirsiniz.
Prerak K

OnActionExecuted içinden isteği nasıl alırım?
user2315985

3

İstek mesajına erişim kolaydır. Sizin taban sınıfı,ApiController içerdiği .Requestözelliği adından da anlaşılacağı gibi, çözümlü formda isteği içeren,. Günlüğe kaydetmek istediğiniz her şeyi inceleyin ve hangisi olursa olsun, kayıt tesisinize iletin. Bu kodu, sadece bir veya bir avuç için yapmanız gerekiyorsa, eyleminizin başlangıcına koyabilirsiniz.

Bunu tüm eylemlerde yapmanız gerekiyorsa (tümü yönetilebilir bir avuçtan fazlası anlamına gelir), yapabileceğiniz şey, .ExecuteAsyncdenetleyiciniz için her eylem çağrısını yakalamak için yöntemi geçersiz kılmaktır.

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

Bunu yapıyorum ve henüz kıyaslamadım, sadece sezgilerim bana bunun çok yavaş olabileceğini mi söylüyor?
Marcus

Neden yavaş olacağını düşünüyorsun? ExecuteAsyncçerçeve tarafından çağrılan şeydir ve temel denetleyici sınıfının uygulaması, eylemi gerçekten yürüten şeydir. Bu, halihazırda gerçekleştirilmekte olan yürütmenin bir parçası olarak günlük kaydınızı çağırmaktır. Buradaki tek ceza, gerçek günlüğe kaydetme zamanıdır.
LB2

Hayır, her isteği kaydederken olduğu gibi "çok yavaş" demek istiyorum.
Marcus

2
Bu bir gereklilik meselesi ve OP tarafından belirtilen gereklilik budur. Bu, sitenin ele aldığı hacim, günlükleme tesisinin performansı vb. İle ilgili bir sorundur. Bu, OP'lerin gönderisinin ötesinde.
LB2

0

Bu oldukça eski bir konu gibi görünüyor, ancak başka bir çözümü paylaşıyor.

HTTP isteği sona erdikten sonra her tetiklenecek olan global.asax dosyanıza bu yöntemi ekleyebilirsiniz.

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

Bu gerçekten eski bir konu ama bunları yapmak için çok zaman harcadım (internette arama yaptım), bu yüzden çözümümü burada yayınlayacağım.

Konsept

  1. Inbound talebini izlemek için APicontroller yönteminin ExecuteAsync değerini geçersiz kıl, çözümümde Base_ApiController'ı projemin API denetleyicilerinin üst öğesi olarak oluşturuyorum.
  2. API denetleyicisinin Giden yanıtını izlemek için System.Web.Http.Filters.ActionFilterAttribute kullanın
  3. *** (Ek) *** İstisna oluştuğunda günlüğe kaydetmek için System.Web.Http.Filters.ExceptionFilterAttribute kullanın.

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

        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.