一、過濾器(Filter)
ASP.NET MVC中的每一個請求,都會分配給對應Controller(以下簡稱“控制器”)下的特定Action(以下簡稱“方法”)處理,正常情況下直接在方法里寫代碼就可以了,但是如果想在方法執行之前或者之后處理一些邏輯,這里就需要用到過濾器。
常用的過濾器有三個:Authorize(授權過濾器),HandleError(異常過濾器),ActionFilter(自定義過濾器),對應的類分別是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,繼承這些類并重寫其中方法即可實現不同的功能。
1.Authorize授權過濾器
授權過濾器顧名思義就是授權用的,授權過濾器在方法執行之前執行,用于限制請求能不能進入這個方法,新建一個方法:
1
2
3
4
|
public JsonResult AuthorizeFilterTest() { return Json( new ReturnModel_Common { msg = "hello world!" }); } |
直接訪問得到結果:
現在假設這個AuthorizeFilterTest方法是一個后臺方法,用戶必須得有一個有效的令牌(token)才能訪問,常規做法是在AuthorizeFilterTest方法里接收并驗證token,但是這樣一旦方法多了,每個方法里都寫驗證的代碼顯然不切實際,這個時候就要用到授權過濾器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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,標注到需要授權才可以訪問的方法上面:
1
2
3
4
5
|
[TokenValidate] public JsonResult AuthorizeFilterTest() { return Json( new ReturnModel_Common { msg = "hello world!" }) } |
標注TokenValidate后,AuthorizeCore方法就在AuthorizeFilterTest之前執行,如果AuthorizeCore返回true,那么授權成功執行AuthorizeFilterTest里面的代碼,否則授權失敗。不傳token:
傳token:
不傳token授權失敗時進入了MVC默認的未授權頁面。這里做下改進:不管授權是成功還是失敗都保證返回值格式一致,方便前端處理,這個時候重寫AuthorizeAttribute類里的HandleUnauthorizedRequest方法即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/// <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端共同使用。
2.HandleError異常過濾器
異常過濾器是處理代碼異常的,在系統的代碼拋錯的時候執行,MVC默認已經實現了異常過濾器,并且注冊到了App_Start目錄下的FilterConfig.cs:
1
|
filters.Add( new HandleErrorAttribute()); |
這個生效于整個系統,任何接口或者頁面報錯都會執行MVC默認的異常處理,并返回一個默認的報錯頁面:Views/Shared/Error(程序發到服務器上報錯時才可以看到本頁面,本地調試權限高,還是可以看到具體報錯信息的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@{ 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方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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注冊下,這樣所有的接口都可以生效了:
1
|
filters.Add( new LogExceptionAttribute()); |
異常過濾器里使用了NLog作為日志記錄工具,Nuget安裝命令:
1
2
|
Install-Package NLog Install-Package NLog.Config |
相比Log4net,NLog配置簡單,僅幾行代碼即可,NLog.config:
1
2
3
4
5
6
7
8
9
10
|
<? 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目錄下,一個項目一個日志目錄,方便管理。全部配置完成,看下代碼:
1
2
3
4
5
|
public JsonResult HandleErrorFilterTest() { int i = int .Parse( "abc" ); return Json( new ReturnModel_Data { data = i }); } |
字符串強轉成int類型,必然報錯,頁面響應:
同時日志也記錄下來了:
3.ActionFilter自定義過濾器
自定義過濾器就更加靈活了,可以精確的注入到請求前、請求中和請求后。繼承抽象類ActionFilterAttribute并重寫里面的方法即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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); } } |
這個過濾器適合做系統操作日志記錄功能:
1
2
3
4
5
6
|
[SystemLog(Operate = "添加用戶" )] public string CustomerFilterTest() { Response.Write( "<br/>Action 執行中..." ); return "<br/>Action 執行結束" ; } |
看下結果:
四個方法執行順序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,非常精確的控制了整個請求過程。
實戰中記錄日志過程是這樣的:在OnActionExecuting方法里寫一條操作日志到數據庫里,全局變量存下這條記錄的主鍵,到OnResultExecuted方法里說明請求結束了,這個時候自然知道用戶的這個操作是否成功了,根據主鍵更新下這條操作日志的是否成功字段。
二、模型綁定(ModelBinder)
先看一個普通的方法:
1
2
3
4
|
public ActionResult Index(Student student) { return View(); } |
這個方法接受的參數是一個Student對象,前端傳遞過來的參數跟Student對象里的屬性保持一直,那么就自動被綁定到這個對象里了,不需要在方法里new Student這個對象并挨個綁定屬性了,綁定的過程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同時繼承了IModelBinder接口,現在就利用IModelBinder接口和DefaultModelBinder來實現更加靈活的模型綁定。
場景一、前端傳過來了一個加密的字符串token,方法里需要用token里的某些字段,那就得在方法里接收這個字符串、解密字符串、轉換成對象,這樣一個方法還好說,多了的話重復代碼非常多,就算提取通用方法,還是要在方法里調用這個通用方法,有沒有辦法直接在參數里就封裝好這個對象?
模型綁定的對象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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方法里進行下注冊:
1
|
ModelBinders.Binders.Add( typeof (TokenModel), new TokenBinder()); |
現在模擬下這個接口:
1
2
3
4
5
|
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就無能為力了。
場景二、去除對象某個屬性的首位空格
1
2
3
4
5
6
7
8
|
public class Student { public int Id { get ; set ; } public string Name { get ; set ; } public string Class { get ; set ; } } |
如果前端傳來的Name屬性有空格,如何去除呢?利用DefaultModelBinder即可實現更靈活的控制
1
2
3
4
5
6
7
8
9
10
11
12
|
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; } } |
標注下需要格式化首位屬性的實體:
1
2
3
4
5
6
7
8
9
10
|
[ModelBinder( typeof (TrimModelBinder))] public class Student { public int Id { get ; set ; } [Trim] public string Name { get ; set ; } public string Class { get ; set ; } } |
好了,測試下:
1
2
3
4
5
6
7
8
9
10
11
|
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。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/oppoic/p/6407896.html?utm_source=tuicool&utm_medium=referral