原文網站: https://www.cnblogs.com/hnsongbiao/p/9414422.html
通過 重寫 OnActionExecutingAsync,來 攔截action的請求消息,當執行OnActionExecutingAsync完成以后才真正進入請求的action中,action運行完后又把控制權給了 OnActionExecutedAsync ,這個管道機制可以使我們用它來輕松實現 權限認證、日志記錄 ,跨域以及很多需要對全局或者部分請求做手腳的的功能。
大概的流程如下:
通過ActionFilterAttribute ,就能攔截action 處理的所有內容,包括請求提交的參數以及返回值。由於asp.net MVC 與webapi 是兩個完全獨立的管道:
MVC由System.Web.Mvc.ActionFilterAttribute 來做action請求的攔截。
webapi 由 System.Web.Http.Filters.ActionFilterAttribute 來處理。
因此攔截action請求是完全不相干的兩個通道,於此同時,當我們需要注冊全局的ActionFilterAttribute 這兩個也是分開注冊的:
MVC 直接在System.Web.Mvc.GlobalFilterCollection 這個全局管道里面注冊 ActionFilter ,位置在App_Start目錄>FilterConfig 類>RegisterGlobalFilters 方法 使用參數filters , filters.Add(new YourMvcAttribute()) 添加你的mvc ActionFilterAttribute 。
wepi API 在System.Web.Http.Filters 中注冊, 在項目的App_Start 目錄>WebApiConfig類中>Register 方法中加入使用 config參數, config.Filters.Add(new YourWebApiAttribute()); 添加你的 webapi ActionFilterAttribute
這樣就可以注冊你的 ActionFilterAttribute 成為全局的Filter,系統中請求經過Action 之前或之后 都會被你的ActionFilter 攔下來做處理然后在轉交下去。
好了道理已經講完了,現在開始我自己要實現的 日志記錄功能,
需求是記錄所有訪問webapi action的(請求地址、內容、訪問用戶、提交的參數、返回的結果、以及一些客戶端的信息)
由於MVC 框架 提倡契約編程,在你自定義的Attribute 時,需要遵守契約規范, 【YourFilterName】+Attribute ,所以我的filter名字為 OperateTrackAttribute
App_Start中創建類OperateTrackAttribute
//參考網站 https://www.cnblogs.com/hnsongbiao/p/9414422.html /// <summary> /// 攔截器——日志 /// </summary> public class OperateTrackAttribute : ActionFilterAttribute { /// <summary> /// 自定義參數 /// </summary> public string msg { get; set; } /// <summary> /// 無參構造函數 /// </summary> public OperateTrackAttribute() { } /// <summary> /// 初始化時填入類的說明 /// </summary> /// <param name="message"></param> public OperateTrackAttribute(string message) { msg = message; } /// <summary> /// 鍵 /// </summary> private static readonly string key = "enterTime"; /// <summary> /// 通過重寫 OnActionExecutingAsync,來攔截action的請求消息, /// 當執行OnActionExecutingAsync完成以后才真正進入請求的action中,action運行完后又把控制權給了 OnActionExecutedAsync , /// 這個管道機制可以使我們用它來輕松實現 權限認證、日志記錄 ,跨域以及很多需要對全局或者部分請求做手腳的的功能。 /// </summary> /// <param name="actionContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) { if (SkipLogging(actionContext))//是否該類標記為NoLog { return base.OnActionExecutingAsync(actionContext, cancellationToken); } //記錄進入請求的時間 actionContext.Request.Properties[key] = DateTime.Now.ToBinary(); return base.OnActionExecutingAsync(actionContext, cancellationToken); } /// <summary> /// 在請求執行完后 記錄請求的數據以及返回數據 /// </summary> /// <param name="actionExecutedContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { try { object beginTime = null; if (actionExecutedContext.Request.Properties.TryGetValue(key, out beginTime)) { DateTime time = DateTime.FromBinary(Convert.ToInt64(beginTime)); HttpRequest request = HttpContext.Current.Request; string token = request.Headers["token"]; WepApiActionLog apiActionLog = new WepApiActionLog { Id = Guid.NewGuid().ToString("N"), // 獲取action名稱 ActionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName, // 獲取Controller 名稱 ControllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName, // 獲取action開始執行的時間 EnterTime = time, // 獲取訪問的ip UserHostAddress = request.UserHostAddress, UserHostName = request.UserHostName, UrlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "", // 獲取request提交的參數 Paramaters = GetRequestValues(actionExecutedContext), // 獲取response響應的結果 ExecuteResult = GetResponseValues(actionExecutedContext), Comments = msg, RequestUri =System.Web.HttpUtility.UrlDecode(request.Url.AbsoluteUri) }; //插入日志文件到數據庫 WriteDB write = new WriteDB(); apiActionLog.WriteDBLog = write.InsertLog(apiActionLog); //異常信息寫入本地 if (apiActionLog.ExecuteResult.Contains("NG: 寫入失敗") || apiActionLog.WriteDBLog != "OK") { WriteTXT(apiActionLog); } } } catch { } return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken); } /// <summary> /// 讀取request 的提交內容 /// </summary> /// <param name="actionExecutedContext"></param> /// <returns></returns> public string GetRequestValues(HttpActionExecutedContext actionExecutedContext) { Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result; stream.Position = 0; Encoding encoding = Encoding.UTF8; /* 這個StreamReader不能關閉,也不能dispose 因為你關掉后,后面的管道 或攔截器就沒辦法讀取了 */ var reader = new StreamReader(stream, encoding); string result = reader.ReadToEnd(); /* 這里也要注意: stream.Position = 0; 當你讀取完之后必須把stream的位置設為開始 因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。 */ stream.Position = 0; return result; } /// <summary> /// 讀取action返回的result /// </summary> /// <param name="actionExecutedContext"></param> /// <returns></returns> public string GetResponseValues(HttpActionExecutedContext actionExecutedContext) { Stream stream = actionExecutedContext.Response.Content.ReadAsStreamAsync().Result; stream.Position = 0; Encoding encoding = Encoding.UTF8; /* 這個StreamReader不能關閉,也不能dispose, 因為你關掉后,后面的管道 或攔截器就沒辦法讀取了 */ var reader = new StreamReader(stream, encoding); string result = reader.ReadToEnd(); /* 這里也要注意: stream.Position = 0; 當你讀取完之后必須把stream的位置設為開始 因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。 */ stream.Position = 0; return result; } /// <summary> /// 判斷類和方法頭上的特性是否要進行Action攔截 /// </summary> /// <param name="actionContext"></param> /// <returns></returns> private static bool SkipLogging(HttpActionContext actionContext) { return actionContext.ActionDescriptor.GetCustomAttributes<NoLogAttribute>().Any() || actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<NoLogAttribute>().Any(); } /// <summary> /// 將錯誤信息保存本地 /// </summary> /// <param name="apiActionLog"></param> public void WriteTXT(WepApiActionLog apiActionLog) { try { //獲取本地服務器路徑 string strDicPath = System.Web.HttpContext.Current.Server.MapPath("~/ErrorLog/" + apiActionLog.ActionName + "/" + System.DateTime.Now.ToString("yyyyMM") + "/"); //創建日志路徑 string strPath = strDicPath + string.Format("{0:yyyy-MM-dd-HH-mm-ss}", apiActionLog.EnterTime) + ".txt"; //如果服務器路徑不存在,就創建一個 if (!Directory.Exists(strDicPath)) Directory.CreateDirectory(strDicPath); //如果日志文件不存在,創建一個 if (!File.Exists(strPath)) using (FileStream fs = File.Create(strPath)) { }; StreamWriter f2 = new StreamWriter(strPath, true, System.Text.Encoding.UTF8); f2.WriteLine("ControllerName:" + apiActionLog.ControllerName); f2.WriteLine("ActionName:" + apiActionLog.ActionName); f2.WriteLine("UserHostAddress:" + apiActionLog.UserHostAddress); f2.WriteLine("RequestUri:" + apiActionLog.RequestUri); f2.WriteLine("Paramaters:" + apiActionLog.Paramaters); f2.WriteLine("ExecuteResult:" + apiActionLog.ExecuteResult); f2.WriteLine("WriteDBLog:" + apiActionLog.WriteDBLog); f2.Close(); f2.Dispose(); } catch(Exception e) { } } }
如果將webapi 的 OperateTrackAttribute 注冊為webapi全局的 ActionFilter 那么我們如果有不想過濾的action 時,可以通過 檢查 方法或類頂部特性 來對那些不需要接受攔擊的 Controller 和action 頂部添加一個這樣的特性來區分開,並通過在filter中檢查是被攔截的action或controller 否包含此特性標記,不包含時攔截。
下面是這個類的寫法,一個空的類 繼承Attribute,並在類頂部寫出該Attribute 使用的范圍:
在App_Start中創建類NoLogAttribute .cs
namespace InsideMesAPI.App_Start { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] public class NoLogAttribute : System.Attribute { } }
這樣我們的攔截就更靈活了,無論是添加了整個個Controller 的攔截還是全局攔截,只需要在不攔截的 controller 或action頭部加上 [NoLog]
例如
/// <summary> /// 記錄該類中的Action內容 /// </summary> [OperateTrack] public class TestApiLogController : ApiController { [HttpPost] public object Login(UserInfo user) { var result = new { data = user, status = true }; return result; } /// <summary> /// 該類不參與記錄 /// </summary> /// <param name="name"></param> /// <returns></returns> [NoLog] public string DontLogMe(string name) { return name; } } 或者 /// <summary> /// 該Controller 下的所有action 都不會被全局的OperateTrack Filter 攔截 /// </summary> [NoLog] public class UserManagerController : ApiController { public List<string> GetUsers() { return new List<string>() { "tomers","jack"}; } public string GiveUserSomeMoney(int money) { return money+""; } }

