webapi 攔截器——日志:自動捕獲請求數據及返回,生成文檔


原文網站: 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+""; } }
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM