詳解ASP.NET MVC 常用擴(kuò)展點(diǎn):過(guò)濾器、模型綁定
一、過(guò)濾器(Filter)
ASP.NET MVC中的每一個(gè)請(qǐng)求,都會(huì)分配給對(duì)應(yīng)Controller(以下簡(jiǎn)稱(chēng)“控制器”)下的特定Action(以下簡(jiǎn)稱(chēng)“方法”)處理,正常情況下直接在方法里寫(xiě)代碼就可以了,但是如果想在方法執(zhí)行之前或者之后處理一些邏輯,這里就需要用到過(guò)濾器。
常用的過(guò)濾器有三個(gè):Authorize(授權(quán)過(guò)濾器),HandleError(異常過(guò)濾器),ActionFilter(自定義過(guò)濾器),對(duì)應(yīng)的類(lèi)分別是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,繼承這些類(lèi)并重寫(xiě)其中方法即可實(shí)現(xiàn)不同的功能。
1.Authorize授權(quán)過(guò)濾器
授權(quán)過(guò)濾器顧名思義就是授權(quán)用的,授權(quán)過(guò)濾器在方法執(zhí)行之前執(zhí)行,用于限制請(qǐng)求能不能進(jìn)入這個(gè)方法,新建一個(gè)方法:
public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }); }
直接訪(fǎng)問(wèn)得到結(jié)果:
現(xiàn)在假設(shè)這個(gè)AuthorizeFilterTest方法是一個(gè)后臺(tái)方法,用戶(hù)必須得有一個(gè)有效的令牌(token)才能訪(fǎng)問(wèn),常規(guī)做法是在AuthorizeFilterTest方法里接收并驗(yàn)證token,但是這樣一旦方法多了,每個(gè)方法里都寫(xiě)驗(yàn)證的代碼顯然不切實(shí)際,這個(gè)時(shí)候就要用到授權(quán)過(guò)濾器:
public class TokenValidateAttribute : AuthorizeAttribute { /// <summary> /// 授權(quán)驗(yàn)證的邏輯處理。返回true則通過(guò)授權(quán),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; } } }
新建了一個(gè)繼承AuthorizeAttribute的類(lèi),并重寫(xiě)了其中的AuthorizeCore方法,這段偽代碼實(shí)現(xiàn)的就是token有值即返回true,沒(méi)有則返回false,標(biāo)注到需要授權(quán)才可以訪(fǎng)問(wèn)的方法上面:
[TokenValidate] public JsonResult AuthorizeFilterTest() { return Json(new ReturnModel_Common { msg = "hello world!" }) }
標(biāo)注TokenValidate后,AuthorizeCore方法就在AuthorizeFilterTest之前執(zhí)行,如果AuthorizeCore返回true,那么授權(quán)成功執(zhí)行AuthorizeFilterTest里面的代碼,否則授權(quán)失敗。不傳token:
傳token:
不傳token授權(quán)失敗時(shí)進(jìn)入了MVC默認(rèn)的未授權(quán)頁(yè)面。這里做下改進(jìn):不管授權(quán)是成功還是失敗都保證返回值格式一致,方便前端處理,這個(gè)時(shí)候重寫(xiě)AuthorizeAttribute類(lèi)里的HandleUnauthorizedRequest方法即可:
/// <summary> /// 授權(quán)失敗處理 /// </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過(guò)期或錯(cuò)誤, msg = "token expired or error" }; json.JsonRequestBehavior = JsonRequestBehavior.AllowGet; filterContext.Result = json; }
效果:
實(shí)戰(zhàn):授權(quán)過(guò)濾器最廣泛的應(yīng)用還是做權(quán)限管理系統(tǒng),用戶(hù)登錄成功后服務(wù)端輸出一個(gè)加密的token,后續(xù)的請(qǐng)求都會(huì)帶上這個(gè)token,服務(wù)端在AuthorizeCore方法里解開(kāi)token拿到用戶(hù)ID,根據(jù)用戶(hù)ID去數(shù)據(jù)庫(kù)里查是否有請(qǐng)求當(dāng)前接口的權(quán)限,有就返回true,反之返回false。這種方式做授權(quán),相比登錄成功給Cookie和Session的好處就是一個(gè)接口PC端、App端共同使用。
2.HandleError異常過(guò)濾器
異常過(guò)濾器是處理代碼異常的,在系統(tǒng)的代碼拋錯(cuò)的時(shí)候執(zhí)行,MVC默認(rèn)已經(jīng)實(shí)現(xiàn)了異常過(guò)濾器,并且注冊(cè)到了App_Start目錄下的FilterConfig.cs:
filters.Add(new HandleErrorAttribute());
這個(gè)生效于整個(gè)系統(tǒng),任何接口或者頁(yè)面報(bào)錯(cuò)都會(huì)執(zhí)行MVC默認(rèn)的異常處理,并返回一個(gè)默認(rèn)的報(bào)錯(cuò)頁(yè)面:Views/Shared/Error(程序發(fā)到服務(wù)器上報(bào)錯(cuò)時(shí)才可以看到本頁(yè)面,本地調(diào)試權(quán)限高,還是可以看到具體報(bào)錯(cuò)信息的)
@{ 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>錯(cuò)誤</title> </head> <body> <hgroup> <h1>錯(cuò)誤。</h1> <h2>處理你的請(qǐng)求時(shí)出錯(cuò)。</h2> </hgroup> </body> </html>
默認(rèn)的異常過(guò)濾器顯然無(wú)法滿(mǎn)足使用需求,重寫(xiě)下異常過(guò)濾器,應(yīng)付項(xiàng)目實(shí)戰(zhàn)中的需求:
1)報(bào)錯(cuò)可以記錄錯(cuò)誤代碼所在的控制器和方法,以及報(bào)錯(cuò)時(shí)的請(qǐng)求參數(shù)和時(shí)間;
2)返回特定格式的JSON方便前端處理。因?yàn)楝F(xiàn)在系統(tǒng)大部分是ajax請(qǐng)求,報(bào)錯(cuò)了返回MVC默認(rèn)的報(bào)錯(cuò)頁(yè)面,前端不好處理
新建一個(gè)類(lèi)LogExceptionAttribute繼承HandleErrorAttribute,并重寫(xiě)內(nèi)部的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.服務(wù)端拋錯(cuò), msg = filterContext.Exception.Message }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } if (filterContext.Result is JsonResult) filterContext.ExceptionHandled = true;//返回結(jié)果是JsonResult,則設(shè)置異常已處理 else base.OnException(filterContext);//執(zhí)行基類(lèi)HandleErrorAttribute的邏輯,轉(zhuǎn)向錯(cuò)誤頁(yè)面 }
異常過(guò)濾器就不像授權(quán)過(guò)濾器一樣標(biāo)注在方法上面了,直接到App_Start目錄下的FilterConfig.cs注冊(cè)下,這樣所有的接口都可以生效了:
filters.Add(new LogExceptionAttribute());
異常過(guò)濾器里使用了NLog作為日志記錄工具,Nuget安裝命令:
Install-Package NLog Install-Package NLog.Config
相比Log4net,NLog配置簡(jiǎn)單,僅幾行代碼即可,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>
如果報(bào)錯(cuò),日志就記錄在D盤(pán)的log目錄下的MVCExtension目錄下,一個(gè)項(xiàng)目一個(gè)日志目錄,方便管理。全部配置完成,看下代碼:
public JsonResult HandleErrorFilterTest() { int i = int.Parse("abc"); return Json(new ReturnModel_Data { data = i }); }
字符串強(qiáng)轉(zhuǎn)成int類(lèi)型,必然報(bào)錯(cuò),頁(yè)面響應(yīng):
同時(shí)日志也記錄下來(lái)了:
3.ActionFilter自定義過(guò)濾器
自定義過(guò)濾器就更加靈活了,可以精確的注入到請(qǐng)求前、請(qǐng)求中和請(qǐng)求后。繼承抽象類(lèi)ActionFilterAttribute并重寫(xiě)里面的方法即可:
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); } }
這個(gè)過(guò)濾器適合做系統(tǒng)操作日志記錄功能:
[SystemLog(Operate = "添加用戶(hù)")] public string CustomerFilterTest() { Response.Write("<br/>Action 執(zhí)行中..."); return "<br/>Action 執(zhí)行結(jié)束"; }
看下結(jié)果:
四個(gè)方法執(zhí)行順序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,非常精確的控制了整個(gè)請(qǐng)求過(guò)程。
實(shí)戰(zhàn)中記錄日志過(guò)程是這樣的:在OnActionExecuting方法里寫(xiě)一條操作日志到數(shù)據(jù)庫(kù)里,全局變量存下這條記錄的主鍵,到OnResultExecuted方法里說(shuō)明請(qǐng)求結(jié)束了,這個(gè)時(shí)候自然知道用戶(hù)的這個(gè)操作是否成功了,根據(jù)主鍵更新下這條操作日志的是否成功字段。
二、模型綁定(ModelBinder)
先看一個(gè)普通的方法:
public ActionResult Index(Student student) { return View(); }
這個(gè)方法接受的參數(shù)是一個(gè)Student對(duì)象,前端傳遞過(guò)來(lái)的參數(shù)跟Student對(duì)象里的屬性保持一直,那么就自動(dòng)被綁定到這個(gè)對(duì)象里了,不需要在方法里new Student這個(gè)對(duì)象并挨個(gè)綁定屬性了,綁定的過(guò)程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同時(shí)繼承了IModelBinder接口,現(xiàn)在就利用IModelBinder接口和DefaultModelBinder來(lái)實(shí)現(xiàn)更加靈活的模型綁定。
場(chǎng)景一、前端傳過(guò)來(lái)了一個(gè)加密的字符串token,方法里需要用token里的某些字段,那就得在方法里接收這個(gè)字符串、解密字符串、轉(zhuǎn)換成對(duì)象,這樣一個(gè)方法還好說(shuō),多了的話(huà)重復(fù)代碼非常多,就算提取通用方法,還是要在方法里調(diào)用這個(gè)通用方法,有沒(méi)有辦法直接在參數(shù)里就封裝好這個(gè)對(duì)象?
模型綁定的對(duì)象:
public class TokenModel { /// <summary> /// 主鍵 /// </summary> public int Id { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { set; get; } /// <summary> /// 簡(jiǎn)介 /// </summary> public string Description { get; set; } }
新建一個(gè)TokenBinder繼承IModelBinder接口并實(shí)現(xiàn)其中的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 }; } } }
這個(gè)方法里接收了一個(gè)token參數(shù),并對(duì)token參數(shù)進(jìn)行了解析和封裝。代碼部分完成了需要到Application_Start方法里進(jìn)行下注冊(cè):
ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());
現(xiàn)在模擬下這個(gè)接口:
public JsonResult TokenBinderTest(TokenModel tokenModel) { var output = "Id:" + tokenModel.Id + ",Name:" + tokenModel.Name + ",Description:" + tokenModel.Description; return Json(new ReturnModel_Common { msg = output }); }
調(diào)用下:
可以看出,“1:汪杰:oppoic.cnblogs.com”已經(jīng)被綁定到tokenModel這個(gè)對(duì)象里面了。但是如果稍復(fù)雜的模型綁定IModelBinder就無(wú)能為力了。
場(chǎng)景二、去除對(duì)象某個(gè)屬性的首位空格
public class Student { public int Id { get; set; } public string Name { get; set; } public string Class { get; set; } }
如果前端傳來(lái)的Name屬性有空格,如何去除呢?利用DefaultModelBinder即可實(shí)現(xiàn)更靈活的控制
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類(lèi)型且有[Trim]標(biāo)記 { return (obj as string).Trim(); } return obj; } }
標(biāo)注下需要格式化首位屬性的實(shí)體:
[ModelBinder(typeof(TrimModelBinder))] public class Student { public int Id { get; set; } [Trim] public string Name { get; set; } public string Class { get; set; } }
好了,測(cè)試下:
public JsonResult TrimBinderTest(Student student) { if (string.IsNullOrEmpty(student.Name) || string.IsNullOrEmpty(student.Class)) { return Json(new ReturnModel_Common { msg = "未找到參數(shù)" }); } else { return Json(new ReturnModel_Common { msg = "Name:" + student.Name + ",長(zhǎng)度:" + student.Name.Length + " Class:" + student.Class + ",長(zhǎng)度:" + student.Class.Length }); } }
可見(jiàn),標(biāo)注了Trim屬性的Name長(zhǎng)度是去除空格的長(zhǎng)度:7,而沒(méi)有標(biāo)注的Class屬性的長(zhǎng)度則是6。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- ASP.NET MVC異常過(guò)濾器用法
- ASP.NET?MVC授權(quán)過(guò)濾器用法
- ASP.NET MVC緩存過(guò)濾器用法
- ASP.NET Core MVC中過(guò)濾器工作原理介紹
- .NET6自定義WebAPI過(guò)濾器
- ASP.NET Core MVC 過(guò)濾器(Filter)
- .Net Core中使用ExceptionFilter過(guò)濾器的方法
- 在Asp.Net Core中使用ModelConvention實(shí)現(xiàn)全局過(guò)濾器隔離
- ASP.NET Core 過(guò)濾器中使用依賴(lài)注入知識(shí)點(diǎn)總結(jié)
- ASP.NET Core MVC 過(guò)濾器的使用方法介紹
- ASP.NET MVC自定義異常過(guò)濾器
相關(guān)文章
關(guān)于C# if語(yǔ)句中并列條件的執(zhí)行
我們知道,當(dāng)兩個(gè)條件進(jìn)行邏輯與操作的時(shí)候,其中任何一個(gè)條件為假,則表達(dá)式的結(jié)果為假。所以,遇到(A 且 B)這種表達(dá)式,如果A為假的話(huà),B是不是真假都無(wú)所謂了,當(dāng)遇到一個(gè)假條件的時(shí)候,程序也就沒(méi)有必要去額外的判斷剩下的東西了2012-02-02Asp.net導(dǎo)出Excel/Csv文本格式數(shù)據(jù)的方法
這篇文章主要介紹了Asp.net導(dǎo)出Excel/Csv文本格式數(shù)據(jù)的方法,比較實(shí)用,需要的朋友可以參考下2014-09-09ASP.NET實(shí)現(xiàn)推送文件到瀏覽器的方法
這篇文章主要介紹了ASP.NET實(shí)現(xiàn)推送文件到瀏覽器的方法,可實(shí)現(xiàn)將文件推送到瀏覽器供用戶(hù)瀏覽或下載的功能,需要的朋友可以參考下2015-06-06詳解免費(fèi)開(kāi)源的DotNet任務(wù)調(diào)度組件Quartz.NET(.NET組件介紹之五)
本篇文章主要介紹免費(fèi)開(kāi)源的DotNet任務(wù)調(diào)度組件Quartz.NET(.NET組件介紹之五),具有一定參考價(jià)值,有興趣的可以了解一下。2016-12-12ASP.NET中實(shí)現(xiàn)jQuery Validation-Engine的Ajax驗(yàn)證實(shí)現(xiàn)代碼
在jQuery的表變驗(yàn)證插件中Validation-Engine是一款高質(zhì)量的產(chǎn)品,提示效果非常精美,而且里面包含了AJAX驗(yàn)證功能2012-05-05asp.net使用JS+form表單Post和Get方式提交數(shù)據(jù)
今天小編就為大家分享一篇關(guān)于asp.net使用JS+form表單Post和Get方式提交數(shù)據(jù),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01基于.NET?7?的?QUIC?實(shí)現(xiàn)?Echo?服務(wù)的詳細(xì)過(guò)程
這篇文章主要介紹了基于.NET?7?的?QUIC實(shí)現(xiàn)Echo服務(wù),下面的內(nèi)容中,我會(huì)介紹如何在.NET?中使用?Quic,文中結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11詳解Spring Boot 中使用 Java API 調(diào)用 lucene
這篇文章主要介紹了詳解Spring Boot 中使用 Java API 調(diào)用 lucene,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11