[笔记][WebAPI] Message Handler 初体验-搭配 NLog 记录所有 WebAPI 的 Request 与 Response

WebAPI 的应用中,有一个 Message Handler ,可以让我们在不改 Web API 的情况下,透过 Message Handler 来处理一些 WebAPI 共同要处理的项目。让这些处理的事情,与 WebAPI 要处理的事情有很好的权责分离,彼此只需要专注处理自己要做的,又能够好好的配合。

小喵刚好有个需求,想要记录某个项目中,WebAPI的进出内容,一来未来当作分析依据,二来保留下来有争议时可以当作依据。借由NLog来记录,因NLog简单易用,对于性能的影响很小,刚好将这两个技术一起搭配来做个练习。怎么做,让我们继续看下去~


缘起

WebAPI 的应用中,有一个 Message Handler ,可以让我们在不改 Web API 的情况下,透过 Message Handler 来处理一些 WebAPI 共同要处理的项目。让这些处理的事情,与 WebAPI 要处理的事情有很好的权责分离,彼此只需要专注处理自己要做的,又能够好好的配合。

小喵刚好有个需求,想要记录某个项目中,WebAPI的进出内容,一来未来当作分析依据,二来保留下来有争议时可以当作依据。借由NLog来记录,因NLog简单易用,对于性能的影响很小,刚好将这两个技术一起搭配来做个练习。怎么做,让我们继续看下去~

本篇重点会放在如何使用,至于概念与教学,会提供相关参考文章的网址,以供参考。

Message Handler简介

Message Handler 的生产线(PipeLine)特性,可以让我们撰写数个 Message Handler 串在一个生产线,就像在进出WebAPI这个大工厂前后,可以有两条生产线,中间设定数个工作区,每个工作区分别执行各自要关注的事情。

相关的详细介绍,建议可以参考:

官方的介绍:http://www.asp.net/web-api/overview/working-with-http/http-message-handlers

(以下图片来自官方网站http://www.asp.net/web-api/overview/working-with-http/http-message-handlers)

webapi_handlers_02[1]

Huan-Lin老师的介绍文章:http://huan-lin.blogspot.com/2013/08/nlog-configuration-example.html

KKBruce的介绍文:http://blog.kkbruce.net/2012/05/aspnet-web-api-8-http-http-message.html#.U97cv_mSxps

NLog简介:

NLog是个用来做Log的好物,透过简单的设定,就能够用来文字、数据库、Windows 事件 Log、Text Console、甚至直接Log到远方的Server(透过TCP/IP)、EMail通知等。

使用上,可以根据不同的情况(等级)做出不同的Log

  • Trace - Very detailed log messages, potentially of a high frequency and volume
  • Debug -Less detailed and/or less frequent debugging messages
  • Info - Informational messages
  • Warn - Warnings which don't appear to the user of the application
  • Error - Error messages
  • Fatal - Fatal error messages. After a fatal error, the application usually terminates.

相关详细的说明,小喵不在这篇里面聊这部分,有兴趣的可以参考以下的资源

  • CodeProject:http://www.codeproject.com/Articles/10631/Introduction-to-NLog
  • 保哥:http://blog.miniasp.com/post/2010/07/18/Useful-Library-NLog-Advanced-NET-Logging.aspx
  • 官方:http://nlog-project.org/

使用上感觉对于性能的影响不大,功能却很强大。这次的练习就以记录为文字档的方式来处理。


到此,简单的介绍用到的两个东西:Message Handler 与 NLog,接着,就是实站的部分,如何撰写来达到想要的目的。

NLog

首先是NLog的一些设定

取得NLog

NLog可以透过NuGet来取得,请取得以下的这几个

  • NLog
  • NLog Schema for Intellisense
  • NLog Configuration

001NlogNuget

设定NLog Configuration

安装完上面所列出来的项目后,就可以在项目中找到【NLog.config】。他是一个xml格式的配置文件,设定哪些等级要Log,以及Log要记录的格式与内容。请参考如下:




  
  
    
    
	
    
    
    

  

  
    
	
	
	
    
	

  

设定上,小喵特别让Log写到项目的App_Data这个特殊的数据夹,由于ASP.NET会特别针对这个数据夹的数据进行保护,也方便移行时可以自动产生,实际上大家可以依据自己的需求来修改。

设定的部分就到此,一个文件而已,很简单。接着,就是 Message Handler 登场。

Message Handler

小喵的需求是透过 Message Handler ,在 Request 与 Response 的时候,将内容(包含Header, Content)给记录下来,这里需要处理三个部分

  • 记录Requesst, Response内容的LogDao
  • Message Handler调用记录内容dao
  • 将写好的Message Handler 透过 Global.asax 加入

以下就分别来看每个部分如何撰写:

撰写LogDao的部分

在Models中,新增一个类【MessageHandlerLogDao】,撰写程序如下:

Imports System.Net.Http

Public Class MessageHandlerLogDao
    '声明NLog的Logger
    Private logger As NLog.Logger = NLog.LogManager.GetCurrentClassLogger()

    ''' 
    ''' 保存Log
    ''' 
    ''' Request(HttpRequestMessage的内容)
    ''' Response(HttpResponseMessage的内容)
    ''' 
    Public Sub SaveLog(ByVal request As HttpRequestMessage, ByVal response As HttpResponseMessage)
        Dim caller As Object = System.Reflection.MethodBase.GetCurrentMethod().Name

        Dim spTxt1 As String = "-----------------------------------------------"
        Dim spTxt2 As String = "==============================================="
        Dim requestContent As String = request.Content.ReadAsStringAsync.Result.ToString
        Dim responseContent As String = response.Content.ReadAsStringAsync.Result.ToString

        logger.Info(vbCrLf & "Request:" & vbCrLf & request.ToString & vbCrLf & "Request Content:" & vbCrLf & requestContent & vbCrLf & spTxt1 & vbCrLf & "Response:" & vbCrLf & response.ToString & vbCrLf & "Response Content : " & vbCrLf & responseContent & vbCrLf & spTxt2 & vbCrLf)


    End Sub
End Class

透过NLog的Info命令,将进出的 Request 与 Response 写入,Message Handler会以异步的方式运行,所以在取得Response.Content的部分,透过ReadAsStringAsync.Result来取得最后真正的Response内容。

撰写 Message Handler

接着,就来撰写Message Handler的部分。我们继续在Models里面,新增一个类【LogOverrideHandler】来当作 Message Handler。这类我们要继承【DelegatingHandler】,并且改写【SendAsync】的方法,来加入我们要做的事情,相关程序如下:

Imports System.Net.Http

Public Class LogOverrideHandler
    Inherits DelegatingHandler

    Protected Overrides Async Function SendAsync(request As HttpRequestMessage, cancellationToken As Threading.CancellationToken) As Threading.Tasks.Task(Of HttpResponseMessage)
        '这里可以针对Request进行额外的处理,这里加一个Header当作范例。
        request.Headers.Add("MyMessageHandlerHead", "SendMessageHandler-" & Format(Now, "yyyyMMddHHmmss"))

        '透过异步方式运行,这里来承接回传时的response内容
        Dim response = Await MyBase.SendAsync(request, cancellationToken)

        '调用LogDao来存Log
        Dim LogDao As New MessageHandlerLogDao
        LogDao.SaveLog(request, response)

        '把response继续往前传
        Return response

    End Function
End Class

将 Message Handler 透过 Global.asax 挂上

最后,我们要把我们写好的 Message Handler 挂上,让他起作用,这时我们要打开 Global.asax 这个文件改写

Imports System.Web.Http
Imports System.Web.Optimization

Public Class WebApiApplication
    Inherits System.Web.HttpApplication

    ''' 
    ''' 挂载Message Handler
    ''' 
    ''' 
    ''' 
    Shared Sub Configure(config As HttpConfiguration)
        '这里可以设定要挂载多组Message Handler
        config.MessageHandlers.Add(New LogOverrideHandler())
        'config.MessageHandlers.Add(New Log2OverrideHandler())
    End Sub

    Protected Sub Application_Start()
        '在Application_Start事件,调用写的Sub挂上
        Configure(GlobalConfiguration.Configuration)

        AreaRegistration.RegisterAllAreas()
        GlobalConfiguration.Configure(AddressOf WebApiConfig.Register)
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
        RouteConfig.RegisterRoutes(RouteTable.Routes)
        BundleConfig.RegisterBundles(BundleTable.Bundles)
    End Sub
End Class

记录的结果

就这样,就可以让 Message Handler 开始运行,针对项目中的所有 Web API,记录下来他们的进(request)与出(response)的相关资讯,以备未来查验。

我们以北风的Products来做范例,记录的结果就像以下这样:

2014-08-04 19:03:24.2200 | INFO | TID:8 | 
Request:
Method: GET, RequestUri: 'http://localhost:39553/api/product/1', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:
{
  Connection: keep-alive
  Accept: text/html
  Accept: application/xhtml+xml
  Accept: application/xml; q=0.9
  Accept: image/webp
  Accept: */*; q=0.8
  Accept-Encoding: gzip
  Accept-Encoding: deflate
  Accept-Encoding: sdch
  Accept-Language: zh-TW
  Accept-Language: zh; q=0.8
  Accept-Language: en-US; q=0.6
  Accept-Language: en; q=0.4
  Host: localhost:39553
  User-Agent: Mozilla/5.0
  User-Agent: (Windows NT 6.1; WOW64)
  User-Agent: AppleWebKit/537.36
  User-Agent: (KHTML, like Gecko)
  User-Agent: Chrome/36.0.1985.125
  User-Agent: Safari/537.36
  MyMessageHandlerHead: SendMessageHandler-20140804190323
  Content-Length: 0
}
Request Content:

-----------------------------------------------
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[MessageHandlerDemo01.ProductInfo, MessageHandlerDemo01, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Headers:
{
  Content-Type: application/xml; charset=utf-8
}
Response Content : 
1false2牛奶每箱24瓶251191740
===============================================
 

2014-08-04 19:05:24.4343 | INFO | TID:11 | 
Request:
Method: GET, RequestUri: 'http://localhost:39553/api/product/34', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:
{
  Connection: keep-alive
  Accept: */*
  Accept-Encoding: gzip
  Accept-Encoding: deflate
  Accept-Encoding: sdch
  Accept-Language: zh-TW
  Accept-Language: zh; q=0.8
  Accept-Language: en-US; q=0.6
  Accept-Language: en; q=0.4
  Host: localhost:39553
  User-Agent: Mozilla/5.0
  User-Agent: (Windows NT 6.1; WOW64)
  User-Agent: AppleWebKit/537.36
  User-Agent: (KHTML, like Gecko)
  User-Agent: Chrome/36.0.1985.125
  User-Agent: Safari/537.36
  MyMessageHandlerHead: SendMessageHandler-20140804190524
  Content-Type: application/json
  Content-Length: 0
}
Request Content:

-----------------------------------------------
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[MessageHandlerDemo01.ProductInfo, MessageHandlerDemo01, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Headers:
{
  Content-Type: application/json; charset=utf-8
}
Response Content : 
{"ProductID":35,"ProductName":"芭乐汁","SupplierID":16,"CategoryID":1,"QuantityPerUnit":"每箱24瓶","UnitPrice":18.0,"UnitsInStock":20,"UnitsOnOrder":0,"ReorderLevel":15,"Discontinued":false}
===============================================
 

2014-08-04 19:05:30.1013 | INFO | TID:12 | 
Request:
Method: GET, RequestUri: 'http://localhost:39553/api/product/45', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:
{
  Connection: keep-alive
  Accept: */*
  Accept-Encoding: gzip
  Accept-Encoding: deflate
  Accept-Encoding: sdch
  Accept-Language: zh-TW
  Accept-Language: zh; q=0.8
  Accept-Language: en-US; q=0.6
  Accept-Language: en; q=0.4
  Host: localhost:39553
  User-Agent: Mozilla/5.0
  User-Agent: (Windows NT 6.1; WOW64)
  User-Agent: AppleWebKit/537.36
  User-Agent: (KHTML, like Gecko)
  User-Agent: Chrome/36.0.1985.125
  User-Agent: Safari/537.36
  MyMessageHandlerHead: SendMessageHandler-20140804190530
  Content-Type: application/json
  Content-Length: 0
}
Request Content:

-----------------------------------------------
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ObjectContent`1[[MessageHandlerDemo01.ProductInfo, MessageHandlerDemo01, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Headers:
{
  Content-Type: application/json; charset=utf-8
}
Response Content : 
{"ProductID":46,"ProductName":"蚵","SupplierID":21,"CategoryID":8,"QuantityPerUnit":"每袋3公斤","UnitPrice":12.0,"UnitsInStock":95,"UnitsOnOrder":0,"ReorderLevel":0,"Discontinued":false}
===============================================

小喵格式没有特别处理,理论上处理成JSON或者XML,对未来的处理会比较方便

末记:

Message Handler 可对于 WebAPI 的所有进/出(request / response),进行一些额外的处理,无论是Log或者进出的Header加解密等等,利用他成双成对,管线(PipeLine)运行、权责分离的这些特性,对于WebAPI的管理来说,提供一个可以额外加工简单而方便的方式。而NLog对于用来记录想记录的资讯,针对不同的状况,可以分别记录在文字档、数据库、事件纪录簿等方式,且性能影响不大,使用上很方便。小喵对于这两者的结合,提供一个范例,自己笔记一下,也提供大家参考。

^_^


以下是签名:

  • 欢迎转贴本站的文章,不过请在贴文主旨上加上【转贴】,并在文章中附上本篇的超链接与站名【topcat姗舞之间的极度凝聚】,感恩大家的配合。
  • 小喵大部分的文章会以小喵熟悉的语言VB.NET撰写,如果您需要C#的Code,也许您可以试着用线上的工具进行转换,这里提供几个参考
    • http://converter.telerik.com/
    • http://www.carlosag.net/tools/codetranslator/
    • http://www.developerfusion.com/tools/convert/vb-to-csharp/

Microsoft MVP
Visual Studio and Development Technologies
(2005~Now)

topcat
Blog:http://www.dotblogs.com.tw/topcat