需要将ASP.NET WebAPI 2请求和响应主体记录到数据库


103

我正在使用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
   }

}

您是否希望在特定控制器中记录特定动作,集合或所有动作的请求/响应?
2014年

只对登录日志感兴趣。(a)发布时间(b)已发布的xml或json正文(c)响应以及Http状态代码(xml或json内容)
user2315985 2014年

我问的原因是建议是否将代码直接应用于行动,或对所有行动采用通用解决方案。请参阅下面的答案。
LB2

仅供参考,我删除了asp.net,因为它与该问题
无关

创建文件管理器不是一种选择吗?
Prera​​k K 2014年

Answers:


192

我建议使用DelegatingHandler。这样,您就不必担心控制器中的任何日志记录代码。

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.WriteLine为您的日志记录代码,然后按以下方式注册处理程序WebApiConfig

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

这是有关消息处理程序的完整Microsoft文档。


3
task.Result.Content返回System.Net.Http.ObjectContent。有没有办法获取原始的xml / json?
PC。

4
@SoftwareFactor:ContinueWithResult有危险的API。await取而代之的是更好的方法,即var result = await base.SendAsync(request, cancellationToken); var resposeBody = await response.Content.ReadAsStringAsync(); Trace.WriteLine(responseBody); return response;
Stephen Cleary

9
这是一个非常酷的解决方案,但是当响应不包含任何正文时,它将引发错误。但这很容易检查和修复:)
buddybubble 2015年

6
调用await request.Content.ReadAsStringAsync();不会导致错误,表明在某些情况下已经读取了请求流吗?
加文

6
如果委托处理程序读取了请求的主体,是否会使实际的终端处理程序(即mvc / webapi)无法使用它?
LB2

15

对于每个WebAPI方法调用,通常有多种方法来处理请求/响应日志记录:

  1. ActionFilterAttribute:可以编写自定义ActionFilterAttribute并装饰控制器/操作方法以启用日志记录。

    缺点:您需要装饰每个控制器/方法(仍然可以在基本控制器上进行装饰,但仍然不能解决跨领域的问题。

  2. 覆盖BaseController并在那里处理日志。

    缺点:我们期望/强制控制器从自定义基本控制器继承。

  3. 使用DelegatingHandler

    优点:我们在这里不使用这种方法来涉及控制器/方法。委托处理程序处于隔离状态,可以优雅地处理请求/响应日志记录。

有关更深入的文章,请参考此http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi


您可以按以下方式分配任何操作过滤器:public static class WebApiConfig {public static void Register(HttpConfiguration config){// Web API配置和服务config.Filters.Add(new MyFilter())// Web API路由config.MapHttpAttributeRoutes (); config.Routes.MapHttpRoute(name:“ DefaultApi”,routeTemplate:“ api / {controller} / {id}”,默认值:new {id = RouteParameter.Optional}); }
米卡·卡尤宁

11

您拥有的一种选择是使用创建动作过滤器并用它装饰WebApiController / ApiMethod。

筛选器属性

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控制器

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

要么

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

希望这可以帮助。


我喜欢这种方法,但是要获得响应,我必须改写OnActionExecuted。问题在于,此时的请求已被转换为我的POCO,而不是xml或json。有什么想法吗?
user2315985 2014年

最初,我的意思是在OnActionExecuting中记录数据,然后简单地让帖子完成其工作。我从您的问题中了解到的是,您只想记录完成的每个帖子的数据。
Prera​​k K

3
我想每次有人发布时都记录请求和响应数据。
user2315985

2
您可以使用OnActionExecuted并尝试“((actionExecutedContext.ActionContext.Response.Content as ObjectContent).Value.ToString()”)来获取响应并将其记录下来。
Prera​​k K

如何从OnActionExecuted中获取请求?
user2315985

3

访问请求消息很容易。您的基类ApiController包含.Request属性,顾名思义,该属性包含解析形式的请求。您只需检查一下它想要记录的内容,然后将其传递给您的记录工具,无论它是哪个。如果您只需要执行一次或执行少量操作,则可以将此代码放在操作的开头。

如果您需要对所有操作执行此操作(所有含义都不仅仅是可管理的少数操作),那么您可以做的是重写.ExecuteAsync方法,以捕获控制器的每个操作调用。

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

我正在执行此操作,但尚未进行基准测试,只是我的直觉告诉我这可能很慢?
Marcus

您为什么认为它会很慢? ExecuteAsync是由框架调用的,而基本控制器类的实现则是实际上使动作执行的东西。这只是调用日志记录,这是已经执行的一部分。他们唯一的惩罚是进行实际记录的时间。
LB2

不,我的意思是,“非常缓慢”,就像记录每个请求一样。
Marcus

2
好吧,这是一个要求的问题,这就是OP规定的要求。这是站点处理量,日志记录工具性能等问题,这超出了OP的发布范围。
LB2

0

这似乎是一个很旧的线程,但是会共享另一个解决方案。

您可以将此方法添加到您的global.asax文件中,该文件将在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.)
            }
        }
    }

0

这确实是一个老话题,但是我花了很多时间(搜索互联网)来做这些事情,所以我将在这里发布我的解决方案。

概念

  1. 覆盖APicontroller方法的ExecuteAsync以跟踪入站请求,在我的解决方案中,我将Base_ApiController创建为项目的API控制器的父级。
  2. 使用System.Web.Http.Filters.ActionFilterAttribute跟踪api控制器的出站响应
  3. ***(附加)***使用System.Web.Http.Filters.ExceptionFilterAttribute记录发生异常时的情况。

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

        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.