在asp.net mvc 中 webapi 和 mvc 處理消息是兩個不一樣的管道,Asp.net mvc 和 webapi 爲咱們提供的 ActionFilterAttribute 攔截器,經過 重寫 OnActionExecutingAsync,來 攔截action的請求消息,當執行OnActionExecutingAsync完成之後才真正進入請求的action中,action運行完後又把控制權給了 OnActionExecutedAsync ,這個管道機制可使咱們用它來輕鬆實現 權限認證、日誌記錄 ,跨域以及不少須要對全局或者部分請求作手腳的的功能。git
大概的流程以下github
經過ActionFilterAttribute ,就能攔截action 處理的全部內容,包括請求提交的參數以及返回值。因爲asp.net MVC 與webapi 是兩個徹底獨立的管道:web
- 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 。api
wepi API 在System.Web.Http.Filters 中註冊, 在項目的App_Start 目錄>WebApiConfig類中>Register 方法中加入使用 config參數, config.Filters.Add(new YourWebApiAttribute()); 添加你的 webapi ActionFilterAttribute 跨域
這樣就能夠註冊你的 ActionFilterAttribute 成爲全局的Filter,系統中請求通過Action 以前或以後 都會被你的ActionFilter 攔下來作處理而後在轉交下去。mvc
好了道理已經講完了,如今開始我本身要實現的 日誌記錄功能,框架
需求是記錄全部訪問webapi action的(請求地址、內容、訪問用戶、提交的參數、返回的結果、以及一些客戶端的信息)asp.net
因爲MVC 框架 提倡契約編程,在你自定義的Attribute 時,須要遵照契約規範, 【YourFilterName】+Attribute ,因此個人filter名字爲 OperateTrackAttributeide
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Http.Controllers; using System.Web.Http.Filters; using WebApiTrackLog.Models; namespace WebApiTrackLog.WebApiAttributes { public class OperateTrackAttribute : ActionFilterAttribute { /// <summary> /// 自定義參數 /// </summary> public string msg { get; set; } public OperateTrackAttribute() { } /// <summary> /// 初始化時填入類的說明 /// </summary> /// <param name="message"></param> public OperateTrackAttribute(string message) { msg = message; } private static readonly string key = "enterTime"; 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) { 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(), //獲取action名稱 actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName, //獲取Controller 名稱 controllerName = actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName, //獲取action開始執行的時間 enterTime = time, //獲取執行action的耗時 costTime = (DateTime.Now - time).TotalMilliseconds, navigator = request.UserAgent, token = token, //獲取用戶token userId = getUserByToken(token), //獲取訪問的ip ip = request.UserHostAddress, userHostName = request.UserHostName, urlReferrer = request.UrlReferrer != null ? request.UrlReferrer.AbsoluteUri : "", browser = request.Browser.Browser + " - " + request.Browser.Version + " - " + request.Browser.Type, //獲取request提交的參數 paramaters = GetRequestValues(actionExecutedContext), 88 //獲取response響應的結果 executeResult = GetResponseValues(actionExecutedContext), comments = msg, RequestUri = request.Url.AbsoluteUri }; using (TrackLogEntities context = new TrackLogEntities()) { context.WepApiActionLogs.Add(apiActionLog); context.SaveChanges(); } } return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken); } /// <summary> /// 獲取當前登陸用戶的id /// </summary> /// <param name="token"></param> /// <returns></returns> public static int getUserByToken(string token) { UserInfo user = null; // TokenManager.getUserByToken(token, out user); return user == null ? 0 : user.user_id; } /// <summary> /// 讀取request 的提交內容 /// </summary> /// <param name="actionExecutedContext"></param> /// <returns></returns> public string GetRequestValues(HttpActionExecutedContext actionExecutedContext) { Stream stream = actionExecutedContext.Request.Content.ReadAsStreamAsync().Result; 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; 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(); } } }
若是將webapi 的 OperateTrackAttribute 註冊爲webapi全局的 ActionFilter 那麼咱們若是有不想過濾的action 時,能夠經過 檢查 方法或類頂部特性 來對那些不須要接受攔擊的 Controller 和action 頂部添加一個這樣的特性來區分開,並經過在filter中檢查是被攔截的action或controller 否包含此特性標記,不包含時攔截。
下面是這個類的寫法,一個空的類 繼承Attribute,並在類頂部寫出該Attribute 使用的範圍
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
public class NoLogAttribute : 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+""; } }
咱們來測試一下: 提交到/api/TestApiLog/Login 整個action 被標記爲攔截
再看看記錄的結果,結果已經記錄了
這樣整個記錄用戶訪問記錄的攔截器就到此爲止了。
須要demo在github中下載: https://github.com/shan333chao/WebApiTrackLogs