詳解ASP.NET MVC 常用擴(kuò)展點:過濾器、模型綁定
一、過濾器(Filter)
ASP.NET MVC中的每一個請求,都會分配給對應(yīng)Controller(以下簡稱“控制器”)下的特定Action(以下簡稱“方法”)處理,正常情況下直接在方法里寫代碼就可以了,但是如果想在方法執(zhí)行之前或者之后處理一些邏輯,這里就需要用到過濾器。
常用的過濾器有三個:Authorize(授權(quán)過濾器),HandleError(異常過濾器),ActionFilter(自定義過濾器),對應(yīng)的類分別是:AuthorizeAttribute、HandleErrorAttribute和ActionFilterAttribute,繼承這些類并重寫其中方法即可實現(xiàn)不同的功能。
1.Authorize授權(quán)過濾器
授權(quán)過濾器顧名思義就是授權(quán)用的,授權(quán)過濾器在方法執(zhí)行之前執(zhí)行,用于限制請求能不能進(jìn)入這個方法,新建一個方法:
public JsonResult AuthorizeFilterTest()
{
return Json(new ReturnModel_Common { msg = "hello world!" });
}
直接訪問得到結(jié)果:

現(xiàn)在假設(shè)這個AuthorizeFilterTest方法是一個后臺方法,用戶必須得有一個有效的令牌(token)才能訪問,常規(guī)做法是在AuthorizeFilterTest方法里接收并驗證token,但是這樣一旦方法多了,每個方法里都寫驗證的代碼顯然不切實際,這個時候就要用到授權(quán)過濾器:
public class TokenValidateAttribute : AuthorizeAttribute
{
/// <summary>
/// 授權(quán)驗證的邏輯處理。返回true則通過授權(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;
}
}
}
新建了一個繼承AuthorizeAttribute的類,并重寫了其中的AuthorizeCore方法,這段偽代碼實現(xiàn)的就是token有值即返回true,沒有則返回false,標(biāo)注到需要授權(quá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)失敗時進(jìn)入了MVC默認(rèn)的未授權(quán)頁面。這里做下改進(jìn):不管授權(quán)是成功還是失敗都保證返回值格式一致,方便前端處理,這個時候重寫AuthorizeAttribute類里的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過期或錯誤,
msg = "token expired or error"
};
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
filterContext.Result = json;
}
效果:

實戰(zhàn):授權(quán)過濾器最廣泛的應(yīng)用還是做權(quán)限管理系統(tǒng),用戶登錄成功后服務(wù)端輸出一個加密的token,后續(xù)的請求都會帶上這個token,服務(wù)端在AuthorizeCore方法里解開token拿到用戶ID,根據(jù)用戶ID去數(shù)據(jù)庫里查是否有請求當(dāng)前接口的權(quán)限,有就返回true,反之返回false。這種方式做授權(quán),相比登錄成功給Cookie和Session的好處就是一個接口PC端、App端共同使用。
2.HandleError異常過濾器
異常過濾器是處理代碼異常的,在系統(tǒng)的代碼拋錯的時候執(zhí)行,MVC默認(rèn)已經(jīng)實現(xiàn)了異常過濾器,并且注冊到了App_Start目錄下的FilterConfig.cs:
filters.Add(new HandleErrorAttribute());
這個生效于整個系統(tǒng),任何接口或者頁面報錯都會執(zhí)行MVC默認(rèn)的異常處理,并返回一個默認(rèn)的報錯頁面:Views/Shared/Error(程序發(fā)到服務(wù)器上報錯時才可以看到本頁面,本地調(diào)試權(quán)限高,還是可以看到具體報錯信息的)
@{
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>
默認(rèn)的異常過濾器顯然無法滿足使用需求,重寫下異常過濾器,應(yīng)付項目實戰(zhàn)中的需求:
1)報錯可以記錄錯誤代碼所在的控制器和方法,以及報錯時的請求參數(shù)和時間;
2)返回特定格式的JSON方便前端處理。因為現(xiàn)在系統(tǒng)大部分是ajax請求,報錯了返回MVC默認(rèn)的報錯頁面,前端不好處理
新建一個類LogExceptionAttribute繼承HandleErrorAttribute,并重寫內(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ù)端拋錯, msg = filterContext.Exception.Message },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
if (filterContext.Result is JsonResult)
filterContext.ExceptionHandled = true;//返回結(jié)果是JsonResult,則設(shè)置異常已處理
else
base.OnException(filterContext);//執(zhí)行基類HandleErrorAttribute的邏輯,轉(zhuǎn)向錯誤頁面
}
異常過濾器就不像授權(quán)過濾器一樣標(biāo)注在方法上面了,直接到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 });
}
字符串強(qiáng)轉(zhuǎn)成int類型,必然報錯,頁面響應(yīng):

同時日志也記錄下來了:

3.ActionFilter自定義過濾器
自定義過濾器就更加靈活了,可以精確的注入到請求前、請求中和請求后。繼承抽象類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);
}
}
這個過濾器適合做系統(tǒng)操作日志記錄功能:
[SystemLog(Operate = "添加用戶")]
public string CustomerFilterTest()
{
Response.Write("<br/>Action 執(zhí)行中...");
return "<br/>Action 執(zhí)行結(jié)束";
}
看下結(jié)果:

四個方法執(zhí)行順序:OnActionExecuting—>OnActionExecuted—>OnResultExecuting—>OnResultExecuted,非常精確的控制了整個請求過程。
實戰(zhàn)中記錄日志過程是這樣的:在OnActionExecuting方法里寫一條操作日志到數(shù)據(jù)庫里,全局變量存下這條記錄的主鍵,到OnResultExecuted方法里說明請求結(jié)束了,這個時候自然知道用戶的這個操作是否成功了,根據(jù)主鍵更新下這條操作日志的是否成功字段。
二、模型綁定(ModelBinder)
先看一個普通的方法:
public ActionResult Index(Student student)
{
return View();
}
這個方法接受的參數(shù)是一個Student對象,前端傳遞過來的參數(shù)跟Student對象里的屬性保持一直,那么就自動被綁定到這個對象里了,不需要在方法里new Student這個對象并挨個綁定屬性了,綁定的過程由MVC中的DefaultModelBinder完成的,DefaultModelBinder同時繼承了IModelBinder接口,現(xiàn)在就利用IModelBinder接口和DefaultModelBinder來實現(xiàn)更加靈活的模型綁定。
場景一、前端傳過來了一個加密的字符串token,方法里需要用token里的某些字段,那就得在方法里接收這個字符串、解密字符串、轉(zhuǎn)換成對象,這樣一個方法還好說,多了的話重復(fù)代碼非常多,就算提取通用方法,還是要在方法里調(diào)用這個通用方法,有沒有辦法直接在參數(shù)里就封裝好這個對象?
模型綁定的對象:
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接口并實現(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 };
}
}
}
這個方法里接收了一個token參數(shù),并對token參數(shù)進(jìn)行了解析和封裝。代碼部分完成了需要到Application_Start方法里進(jìn)行下注冊:
ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());
現(xiàn)在模擬下這個接口:
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這個對象里面了。但是如果稍復(fù)雜的模型綁定IModelBinder就無能為力了。
場景二、去除對象某個屬性的首位空格
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Class { get; set; }
}
如果前端傳來的Name屬性有空格,如何去除呢?利用DefaultModelBinder即可實現(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類型且有[Trim]標(biāo)記
{
return (obj as string).Trim();
}
return obj;
}
}
標(biāo)注下需要格式化首位屬性的實體:
[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 = "未找到參數(shù)" });
}
else
{
return Json(new ReturnModel_Common { msg = "Name:" + student.Name + ",長度:" + student.Name.Length + " Class:" + student.Class + ",長度:" + student.Class.Length });
}
}
可見,標(biāo)注了Trim屬性的Name長度是去除空格的長度:7,而沒有標(biāo)注的Class屬性的長度則是6。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Asp.net導(dǎo)出Excel/Csv文本格式數(shù)據(jù)的方法
這篇文章主要介紹了Asp.net導(dǎo)出Excel/Csv文本格式數(shù)據(jù)的方法,比較實用,需要的朋友可以參考下2014-09-09
詳解免費開源的DotNet任務(wù)調(diào)度組件Quartz.NET(.NET組件介紹之五)
本篇文章主要介紹免費開源的DotNet任務(wù)調(diào)度組件Quartz.NET(.NET組件介紹之五),具有一定參考價值,有興趣的可以了解一下。2016-12-12
ASP.NET中實現(xiàn)jQuery Validation-Engine的Ajax驗證實現(xiàn)代碼
在jQuery的表變驗證插件中Validation-Engine是一款高質(zhì)量的產(chǎn)品,提示效果非常精美,而且里面包含了AJAX驗證功能2012-05-05
asp.net使用JS+form表單Post和Get方式提交數(shù)據(jù)
今天小編就為大家分享一篇關(guān)于asp.net使用JS+form表單Post和Get方式提交數(shù)據(jù),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
基于.NET?7?的?QUIC?實現(xiàn)?Echo?服務(wù)的詳細(xì)過程
這篇文章主要介紹了基于.NET?7?的?QUIC實現(xiàn)Echo服務(wù),下面的內(nèi)容中,我會介紹如何在.NET?中使用?Quic,文中結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-11-11
詳解Spring Boot 中使用 Java API 調(diào)用 lucene
這篇文章主要介紹了詳解Spring Boot 中使用 Java API 調(diào)用 lucene,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11

