ASP.NET MVC中的每個請求,都會分配給對應Controller(如下簡稱「控制器」)下的特定Action(如下簡稱「方法」)處理,正常狀況下直接在方法裏寫代碼就能夠了,可是若是想在方法執行以前或者以後處理一些邏輯,這裏就須要用到過濾器。html
經常使用的過濾器有三個:Authorize(受權過濾器),HandleError(異常過濾器),ActionFilter(自定義過濾器),對應的類分別是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,繼承這些類並重寫其中方法便可實現不一樣的功能。前端
受權過濾器顧名思義就是受權用的,受權過濾器在方法執行以前執行,用於限制請求能不能進入這個方法,新建一個方法:git
public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }); }
直接訪問獲得結果:ajax
如今假設這個AuthorizeFilterTest方法是一個後臺方法,用戶必須得有一個有效的令牌(token)才能訪問,常規作法是在AuthorizeFilterTest方法裏接收並驗證token,可是這樣一旦方法多了,每一個方法裏都寫驗證的代碼顯然不切實際,這個時候就要用到受權過濾器:數據庫
public class TokenValidateAttribute : AuthorizeAttribute { /// <summary> /// 受權驗證的邏輯處理。返回true則經過受權,false則相反 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> protected override bool AuthorizeCore(HttpContextBase httpContext) { string token = httpContext.Request["token"]; if (string.IsNullOrEmpty(token)) { return false; } else { return true; } } }
新建了一個繼承AuthorizeAttribute的類,並重寫了其中的AuthorizeCore方法,這段僞代碼實現的就是token有值即返回true,沒有則返回false,標註到須要受權才能夠訪問的方法上面:json
[TokenValidate] public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }) }
標註TokenValidate後,AuthorizeCore方法就在AuthorizeFilterTest以前執行,若是AuthorizeCore返回true,那麼受權成功執行AuthorizeFilterTest裏面的代碼,不然受權失敗。不傳token:服務器
傳token:mvc
不傳token受權失敗時進入了MVC默認的未受權頁面。這裏作下改進:無論受權是成功仍是失敗都保證返回值格式一致,方便前端處理,這個時候重寫AuthorizeAttribute類裏的HandleUnauthorizedRequest方法便可:ide
/// <summary> /// 受權失敗處理 /// </summary> /// <param name="filterContext"></param> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { base.HandleUnauthorizedRequest(filterContext); var json = new JsonResult(); json.Data = new ReturnModel_Common { success = false, code = ReturnCode_Interface.Token過時或錯誤, msg = "token expired or error" }; json.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = json; }
效果:工具
實戰:受權過濾器最普遍的應用仍是作權限管理系統,用戶登陸成功後服務端輸出一個加密的token,後續的請求都會帶上這個token,服務端在AuthorizeCore方法裏解開token拿到用戶ID,根據用戶ID去數據庫裏查是否有請求當前接口的權限,有就返回true,反之返回false。這種方式作受權,相比登陸成功給Cookie和Session的好處就是一個接口PC端、App端共同使用。
異常過濾器是處理代碼異常的,在系統的代碼拋錯的時候執行,MVC默認已經實現了異常過濾器,而且註冊到了App_Start目錄下的FilterConfig.cs:
filters.Add(new HandleErrorAttribute());
這個生效於整個系統,任何接口或者頁面報錯都會執行MVC默認的異常處理,並返回一個默認的報錯頁面:Views/Shared/Error(程序發到服務器上報錯時才能夠看到本頁面,本地調試權限高,仍是能夠看到具體報錯信息的)
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width" /> <title>錯誤</title> </head> <body> <hgroup> <h1>錯誤。</h1> <h2>處理你的請求時出錯。</h2> </hgroup> </body> </html>
默認的異常過濾器顯然沒法知足使用需求,重寫下異常過濾器,應付項目實戰中的需求:
1)報錯能夠記錄錯誤代碼所在的控制器和方法,以及報錯時的請求參數和時間;
2)返回特定格式的JSON方便前端處理。由於如今系統大部分是ajax請求,報錯了返回MVC默認的報錯頁面,前端很差處理
新建一個類LogExceptionAttribute繼承HandleErrorAttribute,並重寫內部的OnException方法:
public override void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled) { string controllerName = (string)filterContext.RouteData.Values["controller"]; string actionName = (string)filterContext.RouteData.Values["action"]; string param = Common.GetPostParas(); string ip = HttpContext.Current.Request.UserHostAddress; LogManager.GetLogger("LogExceptionAttribute").Error("Location:{0}/{1} Param:{2}UserIP:{3} Exception:{4}", controllerName, actionName, param, ip, filterContext.Exception.Message); filterContext.Result = new JsonResult { Data = new ReturnModel_Common { success = false, code = ReturnCode_Interface.服務端拋錯, msg = filterContext.Exception.Message }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } if (filterContext.Result is JsonResult) filterContext.ExceptionHandled = true;//返回結果是JsonResult,則設置異常已處理 else base.OnException(filterContext);//執行基類HandleErrorAttribute的邏輯,轉向錯誤頁面 }
異常過濾器就不像受權過濾器同樣標註在方法上面了,直接到App_Start目錄下的FilterConfig.cs註冊下,這樣全部的接口均可以生效了:
filters.Add(new LogExceptionAttribute());
異常過濾器裏使用了NLog做爲日誌記錄工具,Nuget安裝命令:
Install-Package NLog
Install-Package NLog.Config
相比Log4net,NLog配置簡單,僅幾行代碼便可,NLog.config:
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <target xsi:type="File" name="f" fileName="${basedir}/log/${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" /> <target xsi:type="File" name="f2" fileName="D:\log\MVCExtension\${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" /> </targets> <rules> <logger name="*" minlevel="Debug" writeTo="f2" /> </rules> </nlog>
若是報錯,日誌就記錄在D盤的log目錄下的MVCExtension目錄下,一個項目一個日誌目錄,方便管理。所有配置完成,看下代碼:
public JsonResult HandleErrorFilterTest() { int i = int.Parse("abc"); return Json(new ReturnModel_Data { data = i }); }
字符串強轉成int類型,必然報錯,頁面響應:
同時日誌也記錄下來了:
自定義過濾器就更加靈活了,能夠精確的注入到請求前、請求中和請求後。繼承抽象類ActionFilterAttribute並重寫裏面的方法便可:
public class SystemLogAttribute : ActionFilterAttribute { public string Operate { get; set; } public override void OnActionExecuted(ActionExecutedContext filterContext) { filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuted"); base.OnActionExecuted(filterContext); } public override void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuting"); base.OnActionExecuting(filterContext); } public override void OnResultExecuted(ResultExecutedContext filterContext) { filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuted"); base.OnResultExecuted(filterContext); } public override void OnResultExecuting(ResultExecutingContext filterContext) { filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuting"); base.OnResultExecuting(filterContext); } }
這個過濾器適合作系統操做日誌記錄功能:
[SystemLog(Operate = "添加用戶")] public string CustomerFilterTest() { Response.Write("<br/>Action 執行中..."); return "<br/>Action 執行結束"; }
看下結果:
四個方法執行順序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,很是精確的控制了整個請求過程。
實戰中記錄日誌過程是這樣的:在OnActionExecuting方法裏寫一條操做日誌到數據庫裏,全局變量存下這條記錄的主鍵,到OnResultExecuted方法裏說明請求結束了,這個時候天然知道用戶的這個操做是否成功了,根據主鍵更新下這條操做日誌的是否成功字段。
擴展閱讀:
先看一個普通的方法:
public ActionResult Index(Student student) { return View(); }
這個方法接受的參數是一個Student對象,前端傳遞過來的參數跟Student對象裏的屬性保持一直,那麼就自動被綁定到這個對象裏了,不須要在方法裏new Student這個對象並挨個綁定屬性了,綁定的過程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同時繼承了IModelBinder接口,如今就利用IModelBinder接口和DefaultModelBinder來實現更加靈活的模型綁定。
模型綁定的對象:
public class TokenModel { /// <summary> /// 主鍵 /// </summary> public int Id { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { set; get; } /// <summary> /// 簡介 /// </summary> public string Description { get; set; } }
新建一個TokenBinder繼承IModelBinder接口並實現其中的BindModel方法:
public class TokenBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var token = controllerContext.HttpContext.Request["token"]; if (!string.IsNullOrEmpty(token)) { string[] array = token.Split(':'); if (array.Length == 3) { return new TokenModel() { Id = int.Parse(array[0]), Name = array[1], Description = array[2] }; } else { return new TokenModel() { Id = 0 }; } } else { return new TokenModel() { Id = 0 }; } } }
這個方法裏接收了一個token參數,並對token參數進行了解析和封裝。代碼部分完成了須要到Application_Start方法裏進行下注冊:
ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());
如今模擬下這個接口:
public JsonResult TokenBinderTest(TokenModel tokenModel) { var output = "Id:" + tokenModel.Id + ",Name:" + tokenModel.Name + ",Description:" + tokenModel.Description; return Json(new ReturnModel_Common { msg = output }); }
調用下:
能夠看出,「1:汪傑:oppoic.cnblogs.com」已經被綁定到tokenModel這個對象裏面了。可是若是稍複雜的模型綁定IModelBinder就無能爲力了。
public class Student { public int Id { get; set; } public string Name { get; set; } public string Class { get; set; } }
若是前端傳來的Name屬性有空格,如何去除呢?利用DefaultModelBinder便可實現更靈活的控制
public class TrimModelBinder : DefaultModelBinder { protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { var obj = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); if (obj is string && propertyDescriptor.Attributes[typeof(TrimAttribute)] != null)//判斷是string類型且有[Trim]標記 { return (obj as string).Trim(); } return obj; } }
標註下須要格式化首位屬性的實體:
[ModelBinder(typeof(TrimModelBinder))] public class Student { public int Id { get; set; } [Trim] public string Name { get; set; } public string Class { get; set; } }
好了,測試下:
public JsonResult TrimBinderTest(Student student) { if (string.IsNullOrEmpty(student.Name) || string.IsNullOrEmpty(student.Class)) { return Json(new ReturnModel_Common { msg = "未找到參數" }); } else { return Json(new ReturnModel_Common { msg = "Name:" + student.Name + ",長度:" + student.Name.Length + " Class:" + student.Class + ",長度:" + student.Class.Length }); } }
可見,標註了Trim屬性的Name長度是去除空格的長度:7,而沒有標註的Class屬性的長度則是6。
擴展閱讀:
ASP.NET MVC: 使用自定義 ModelBinder
ASP.NET MVC: 使用自定義 ModelBinder 過濾敏感信息
爬蟲可恥,本文原始連接:http://www.cnblogs.com/oppoic/p/6407896.html
環境:Visual Studio 201三、ASP.NET MVC 5.0 源碼點我