選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
4 分鐘閱讀時間 (899 個字)

透過 Asp.Net MVC Filter 實作 Controller 層級的 Action Logging 機制

透過 Asp.Net MVC Filter 實作 Controller 層級的 Action Logging 機制
.Net MVC Filter 有四種類型:Authorization(驗證)、Action(動作)、Result(結果)、Exception(例外),
有關各類型的介紹可參考 MSDN 的 Filtering in ASP.NET MVC 介紹,
以下本文將簡單介紹如何使用 Asp.Net MVC 中的 Action filters 來實作 Controller 層級 Action Logging 機制 ..

前言
簡單來說,
我們可以將 Asp.Net MVC Filter 視為每個指定目標的「Interceptor(攔截器)」,
在各攔截器中可以去做很多我們想做的事情, 例如:可以使用 Authorization Filter 攔截器進行 FormsAuthentication 或 Session  狀態的驗證; 可以使用 Exception Filter 攔截器進行 Exception Catch 並進行 Logging, 或是使用其他類型的攔截器實作其他事情 ... 等 
身為一個開發者,
常常會收到客戶丟過來的不知名錯誤或問題單,
為了能在最短的時間內,
透過系統的 Log 了解客戶到底傳送了什麼資料到後端,
這時候 Action Filter 攔截器即大大發揮了它的效益 ..

※ 備註:本文搭配 NLog 套件實作 Logging 機制, 於此不針對 NLog 多做介紹
關於 Action filters

實作 Action Log 機制
Step 1. 首先, 我們先在 Controller 覆寫(Override)OnActionExecuting 事件
[code language="cpp"]
/// <summary>
/// 執行 Action 觸發事件
/// </summary>
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);

// TODO:準備開始實作
}
[/code]

Step 2. 接下來, 透過 JSON.NET 解析 ActionExecutingContext 中的 ActionParameters(參數資訊)
[code language="cpp"]
// 參數資訊
string parametersInfo = JsonConvert.SerializeObject(filterContext.ActionParameters, new JsonSerializerSettings()
{
ContractResolver = new ReadablePropertiesOnlyResolver()
});
[/code]

可以看到, 上述程式針對物件進行 Json Convert 的時候, 有額外加上 Json 解析器的設定,
目的是為了保留或去除我們所要看到的資訊,
因為從前端往 Controller 傳送的資料類型可能會包含檔案資訊,
為了提升 Log 的易讀性, 故把 Stream 相關 Type 資訊過濾掉,
以下為該解析器(ReadablePropertiesOnlyResolver.cs)的程式:

[code language="cpp"]
/// <summary>
/// JsonSerializer 讀取屬性的解析器設定
/// </summary>
class ReadablePropertiesOnlyResolver : DefaultContractResolver
{
/// <summary>
/// 建立可呈現(解析)的屬性
/// </summary>
/// <returns>呈現的屬性</returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (typeof(Stream).IsAssignableFrom(property.PropertyType))
{
property.Ignored = true;
}
return property;
}
}
[/code]

Step 3. 再來, 進一步從 ActionExecutingContext  中解析出 Runtime 的 Controller 或 Action 資訊
[code language="cpp"]
string controllerName = filterContext.Controller.GetType().Name;
string actionName = filterContext.ActionDescriptor.ActionName;
[/code]

Step 4. 最後, 我們將訊息打包後寫入 Log, 即完成 Action Logging 啦
[code language="cpp"]
// 訊息內容
string message = string.Format(
"{0}.{1}() => {2}",
controllerName,
actionName,
string.IsNullOrEmpty(parametersInfo) ? "(void)" : parametersInfo
);

// 寫入訊息(透過 NLog 套件)
logger.Info(message);
[/code]

Final. 完整的程式碼
[code language="cpp"]
/// <summary>
/// 執行 Action 觸發事件
/// </summary>
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);

// 參數資訊
string parametersInfo = JsonConvert.SerializeObject(filterContext.ActionParameters, new JsonSerializerSettings()
{
ContractResolver = new ReadablePropertiesOnlyResolver()
});

// 運行中的 Controller & Action 資訊
string controllerName = filterContext.Controller.GetType().Name;
string actionName = filterContext.ActionDescriptor.ActionName;

// 訊息內容
string message = string.Format(
"{0}.{1}() => {2}",
controllerName,
actionName,
string.IsNullOrEmpty(parametersInfo) ? "(void)" : parametersInfo
);

// 寫入訊息
Logger logger = LogManager.GetCurrentClassLogger();
logger.Info(message);
}
[/code]

Demo. 結果展示(Log File)

進階實作 Action Log 機制:掛載自定義的 Attribute
上述的作法是直接在 Controller 中 Override OnActionExecuting 事件,
作者本身覺得這樣的寫法會讓程式碼看起來沒那麼簡潔,
於是整合了 C# 所提供的一個定義宣告式標記(Tag)的機制:屬性(Attribute),
透過自訂一個攔截器 Attribute , 並掛載至指定 Controller 層級的 Class 或 Action 上,
即可達成預期的 Logging 機制 ..

Step 1. 自訂攔截器 Attribute(InterceptorOfControllerAttribute), 設定允許掛載在 Class 或 Action 上
[code language="cpp"]
using Newtonsoft.Json;
using NLog;
using System;
using System.Web.Mvc;

namespace AopLogger.Filters
{
/// <summary>
/// Controller 攔截器擴增屬性
/// </summary>
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
sealed class InterceptorOfControllerAttribute : ActionFilterAttribute
{
/// <summary>
/// 執行 Action 觸發事件
/// </summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// 訊息管理器
Logger logger = LogManager.GetCurrentClassLogger();

// 參數資訊
string parametersInfo = JsonConvert.SerializeObject(filterContext.ActionParameters, new JsonSerializerSettings()
{
ContractResolver = new ReadablePropertiesOnlyResolver()
});

// 運行中的 Controller & Action 資訊
string controllerName = filterContext.Controller.GetType().Name;
string actionName = filterContext.ActionDescriptor.ActionName;

// 訊息內容
string message = string.Format(
"{0}.{1}() => {2}",
controllerName,
actionName,
string.IsNullOrEmpty(parametersInfo) ? "(void)" : parametersInfo
);

// 寫入訊息
logger.Info(message);
}
}
}
[/code]

Step 2. 將自訂的攔截器 Attribute 掛載至指定 Controller 層級的 Class 或 Action 上即完成
[code language="cpp"]
using AopLogger.Filters;
using AopLogger.Models;
using AopLogger.Services;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;

namespace AopLogger.Controllers
{
[InterceptorOfController]
public class LoggerController : Controller
{
/// <summary>
/// 測試首頁
/// </summary>
public ActionResult Index(TestModel paramA, string paramB)
{
return View();
}
}
}
[/code]
【Asp.Net MVC】使用 ContextBoundObject 搭配 Attribute 實現...
AP Server連接File Server出現異常錯誤

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2024/05/04, 週六

Captcha 圖像