需要将 asp.net webapi 2请求和响应主体记录到数据库中

我正在使用 IIS 上托管的 Microsoft Asp.net WebApi2。我非常简单地想记录每篇文章的请求主体(XML 或 JSON)和响应主体。

这个项目和处理邮件的控制器没有什么特别之处。我对使用日志框架(如 nLog、 elmah、 log4net 或 Web API 的内置跟踪特性)不感兴趣,除非有必要这样做。

我只是想知道我的日志代码放在哪里,以及如何从传入和传出的请求和响应中获得实际的 JSON 或 XML。

我的控制器方法:

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


}
90236 次浏览

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.

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

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.

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.

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

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


}
}
}