Perlu mencatat permintaan dan respons asp.net webapi 2 ke database

103

Saya menggunakan Microsoft Asp.net WebApi2 yang dihosting di IIS. Saya hanya ingin mencatat badan permintaan (XML atau JSON) dan badan respons untuk setiap posting.

Tidak ada yang istimewa tentang proyek ini atau pengontrol yang memproses pos. Saya tidak tertarik menggunakan kerangka kerja logging seperti nLog, elmah, log4net, atau fitur pelacakan bawaan dari API web kecuali jika diperlukan.

Saya hanya ingin tahu di mana harus meletakkan kode logging saya dan bagaimana mendapatkan JSON atau XML yang sebenarnya dari permintaan dan respons yang masuk dan keluar.

Metode posting pengontrol saya:

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

}
pengguna2315985
sumber
Apakah Anda ingin mencatat Permintaan / Tanggapan untuk tindakan tertentu, sekumpulan, atau semua tindakan Anda dalam pengontrol tertentu?
LB2
Hanya tertarik untuk masuk Posting. (a) Waktu Posting (b) isi xml atau json diposting (c) respons (konten xml atau json) bersama dengan Kode Status Http
pengguna2315985
Alasan saya bertanya adalah untuk menyarankan apakah akan menerapkan kode secara langsung, atau solusi umum untuk semua tindakan. Lihat jawaban saya di bawah.
LB2
FYI saya menghapus asp.net karena tidak ada hubungannya dengan pertanyaan ini
Dalorzo
apakah membuat filer bukanlah suatu pilihan?
Prerak K

Jawaban:

194

Saya akan merekomendasikan menggunakan file DelegatingHandler. Maka Anda tidak perlu khawatir tentang kode logging apa pun di pengontrol Anda.

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

Cukup ganti Trace.WriteLinedengan kode logging Anda dan daftarkan penangannya WebApiConfigseperti ini:

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

Berikut adalah dokumentasi Microsoft lengkap untuk Penangan Pesan .

SoftwareFactor
sumber
3
task.Result.Contentkembali System.Net.Http.ObjectContent. Apakah ada cara untuk mendapatkan xml / json mentah sebagai gantinya?
PC.
4
@SoftwareFactor: ContinueWithdan Resultmerupakan API yang berbahaya. Akan jauh lebih baik jika digunakan awaitsebagai gantinya, yaituvar result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary
9
Ini adalah solusi yang sangat keren, namun akan menimbulkan kesalahan saat respons tidak berisi isi. Tapi itu cukup mudah untuk diperiksa dan diperbaiki :)
buddybubble
6
Apakah panggilan untuk await request.Content.ReadAsStringAsync();tidak menghasilkan kesalahan yang mengatakan bahwa aliran permintaan telah dibaca dalam keadaan tertentu?
Gavin
6
Jika penangan pendelegasi membaca isi permintaan, bukankah itu membuatnya tidak tersedia untuk penangan terminal yang sebenarnya (yaitu mvc / webapi)?
LB2
15

Ada beberapa pendekatan untuk menangani pencatatan Permintaan / Respons secara umum untuk setiap panggilan metode WebAPI:

  1. ActionFilterAttribute: Seseorang dapat menulis kustom ActionFilterAttributedan menghias metode pengontrol / tindakan untuk mengaktifkan logging.

    Kontra: Anda perlu menghias setiap pengontrol / metode (Anda masih bisa melakukannya di pengontrol dasar, tetapi tetap tidak mengatasi masalah lintas sektoral.

  2. Timpa BaseControllerdan tangani logging di sana.

    Kontra: Kami mengharapkan / memaksa pengontrol untuk mewarisi dari pengontrol basis khusus.

  3. Menggunakan DelegatingHandler.

    Keuntungan: Kami tidak menyentuh pengontrol / metode di sini dengan pendekatan ini. Penangan pendelegasi duduk dalam isolasi dan dengan baik menangani pencatatan permintaan / tanggapan.

Untuk artikel yang lebih mendalam, lihat http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi ini .

Venkatesh Muniyandi
sumber
Anda dapat menetapkan filter tindakan apa pun sebagai berikut: public static class WebApiConfig {public static void Register (HttpConfiguration config) {// Konfigurasi Web API dan layanan config.Filters.Add (new MyFilter ()) // Web API merutekan config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute (nama: "DefaultApi", routeTemplate: "api / {controller} / {id}", default: baru {id = RouteParameter.Optional}); }}
Mika Karjunen
11

Salah satu opsi yang Anda miliki adalah menggunakan membuat filter tindakan dan menghias WebApiController / ApiMethod dengannya.

Atribut Filter

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

Pengontrol WebApi

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

atau

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

Semoga ini membantu.

Prerak K
sumber
Saya suka pendekatan ini tetapi untuk mendapatkan respons saya harus mengganti OnActionExecuted sebagai gantinya. Masalahnya adalah permintaan pada saat itu telah diubah menjadi POCO saya alih-alih menjadi xml atau json. Ada pemikiran?
pengguna2315985
Awalnya saya maksudkan, data log di OnActionExecuting dan kemudian biarkan posting melakukan tugasnya. Apa yang saya mengerti dari pertanyaan Anda adalah bahwa Anda hanya ingin mencatat data untuk setiap posting yang sudah selesai.
Prerak K
3
Saya ingin mencatat permintaan dan data respons setiap kali seseorang memposting.
pengguna2315985
2
Anda dapat menggunakan OnActionExecuted dan mencoba "(actionExecutedContext.ActionContext.Response.Content sebagai ObjectContent) .Value.ToString ()" untuk mendapatkan respons dan mencatatnya.
Prerak K
Bagaimana cara mendapatkan permintaan dari dalam OnActionExecuted?
pengguna2315985
3

Mendapatkan akses untuk meminta pesan itu mudah. Kelas dasarApiController Anda , berisi .Requestproperti , yang, seperti namanya, berisi permintaan dalam bentuk parsing. Anda cukup memeriksanya untuk apa pun yang ingin Anda catat dan meneruskannya ke fasilitas logging Anda, mana pun itu. Kode ini dapat Anda letakkan di awal tindakan Anda, jika Anda perlu melakukannya hanya untuk satu atau sedikit.

Jika Anda perlu melakukannya pada semua tindakan (semua berarti lebih dari segelintir yang dapat dikelola), maka yang dapat Anda lakukan adalah mengganti .ExecuteAsyncmetode untuk menangkap setiap panggilan tindakan untuk pengontrol Anda.

public override Task<HttpResponseMessage> ExecuteAsync(
    HttpControllerContext controllerContext,
    CancellationToken cancellationToken
)
{
    // Do logging here using controllerContext.Request
    return base.ExecuteAsync(controllerContext, cancellationToken);
}
LB2
sumber
Saya melakukan ini, dan saya belum membandingkannya, hanya intuisi saya yang memberi tahu bahwa ini bisa sangat lambat?
Marcus
Menurut Anda mengapa itu akan lambat? ExecuteAsyncadalah apa yang disebut oleh kerangka kerja, dan implementasi kelas pengontrol dasar adalah apa yang sebenarnya membuat tindakan dieksekusi. Ini hanya memanggil ke logging Anda sebagai bagian dari eksekusi yang sudah terjadi. Hukuman mereka hanya di sini adalah waktu untuk melakukan penebangan yang sebenarnya.
LB2
Tidak, maksud saya, 'sangat lambat' seperti dalam mencatat setiap permintaan.
Marcus
2
Nah, itulah pertanyaan persyaratan, dan itulah persyaratan yang dinyatakan oleh OP. Ini pertanyaan tentang volume yang ditangani situs, kinerja fasilitas logging, dll. Itu di luar posting OP.
LB2
0

Ini tampaknya merupakan utas yang cukup lama tetapi bekerja berbagi solusi lain.

Anda dapat menambahkan metode ini di file global.asax Anda yang akan dipicu setiap setelah permintaan HTTP berakhir.

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
sumber
0

Ini benar-benar topik lama tetapi saya menghabiskan banyak waktu (mencari di internet) untuk melakukan hal ini jadi saya akan memposting solusi saya di sini.

Konsep

  1. Override ExecuteAsync of APicontroller method untuk melacak permintaan Inbound, dalam solusi saya, saya membuat Base_ApiController sebagai induk dari pengontrol API proyek saya.
  2. Gunakan System.Web.Http.Filters.ActionFilterAttribute untuk melacak respons Keluar dari pengontrol api
  3. *** (Tambahan) *** Gunakan System.Web.Http.Filters.ExceptionFilterAttribute ke log ketika terjadi pengecualian.

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

        }
    }
}
pengguna3682728
sumber