Need to log asp.net webapi 2 request and response body to a database

C#asp.net Web-Api

C# Problem Overview


I am using Microsoft Asp.net WebApi2 hosted on IIS. I very simply would like to log the request body (XML or JSON) and the response body for each post.

There is nothing special about this project or the controller processing the post. I am not interested in using logging frameworks like nLog, elmah, log4net, or the built-in tracing features of web API unless it is necessary to do so.

I am simply wanting to know where to put my logging code and how to get the actual JSON or XML from the incoming and outgoing request and response.

My controller post method:

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

}

C# Solutions


Solution 1 - C#

I would recommend using a DelegatingHandler. Then you will not need to worry about any logging code in your controllers.

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

Just replace Trace.WriteLine with your logging code and register the handler in WebApiConfig like this:

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

Here is the full Microsoft documentation for Message Handlers.

Solution 2 - C#

There are multiple approaches to generically handle Request/Response logging for every WebAPI method calls:

  1. ActionFilterAttribute: One can write custom ActionFilterAttribute and decorate the controller/action methods to enable logging.

    Con: You need to decorate every controller/methods (still you can do it on base controller, but still it doesn't address cross cutting concerns.

  2. Override BaseController and handle logging there.

    Con: We are expecting/forcing the controllers to inherit from a custom base controller.

  3. Using DelegatingHandler.

    Advantage: We are not touching controller/method here with this approach. Delegating handler sits in isolation and gracefully handles the request/response logging.

For more indepth article, refer this http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi.

Solution 3 - C#

One of the option you have is using creating a action filter and decorating your WebApiController/ApiMethod with it.

Filter Attribute

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

or

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

Hope this helps.

Solution 4 - C#

Getting access to request message is easy. Your base class, ApiController contains .Request property, which, as name suggests, contains the request in parsed form. You simply examine it for whatever you're looking to log and pass it to your logging facility, whichever it may be. This code you can put in the beginning of your action, if you need to do it for just one or a handful.

If you need to do it on all actions (all meaning more than a manageable handful), then what you can do is override .ExecuteAsync method to capture every action call for your controller.

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

Solution 5 - C#

This seems to be a pretty old thread but worh sharing another solution.

You can add this method in your global.asax file which will be triggered every after HTTP request ends.

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

Solution 6 - C#

This is really old topic but I spent much time(search the internet) to do these thing so I will just post my solution here.

Concept

  1. Override ExecuteAsync of APicontroller method for tracking Inbound request,in my solution I create Base_ApiController as a parent of my project's API controllers .
  2. Use System.Web.Http.Filters.ActionFilterAttribute to track Outbound response of api controller
  3. ***(Additional)***Use System.Web.Http.Filters.ExceptionFilterAttribute to log when exception occure.

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

        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser2315985View Question on Stackoverflow
Solution 1 - C#SoftwareFactorView Answer on Stackoverflow
Solution 2 - C#Venkatesh MuniyandiView Answer on Stackoverflow
Solution 3 - C#Prerak KView Answer on Stackoverflow
Solution 4 - C#LB2View Answer on Stackoverflow
Solution 5 - C#EMCView Answer on Stackoverflow
Solution 6 - C#user3682728View Answer on Stackoverflow