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

透過 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]
×
Stay Informed

When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.

【Asp.Net MVC】使用 ContextBoundObject 搭配 Attribute 實現...
AP Server連接File Server出現異常錯誤

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
2025年10月01日, 星期三

Captcha 圖像