APS.NET MVC中(以下簡稱“MVC”)的每一個請求,都會分配給相應的控制器和對應的行為方法去處理,而在這些處理的前前后后如果想再加一些額外的邏輯處理。這時候就用到了過濾器。
MVC支持的過濾器類型有四種,分別是:Authorization(授權),Action(行為),Result(結果)和Exception(異常)。如下表,
| 過濾器類型 |
接口 |
描述 |
| Authorization |
IAuthorizationFilter |
此類型(或過濾器)用於限制進入控制器或控制器的某個行為方法 |
| Exception |
IExceptionFilter |
用於指定一個行為,這個被指定的行為處理某個行為方法或某個控制器里面拋出的異常 |
| Action |
IActionFilter |
用於進入行為之前或之后的處理 |
| Result |
IResultFilter |
用於返回結果的之前或之后的處理 |
但是默認實現它們的過濾器只有三種,分別是Authorize(授權),ActionFilter,HandleError(錯誤處理);各種信息如下表所示
| 過濾器 |
類名 |
實現接口 |
描述 |
| ActionFilter |
AuthorizeAttribute |
IAuthorizationFilter |
此類型(或過濾器)用於限制進入控制器或控制器的某個行為方法 |
| HandleError |
HandleErrorAttribute |
IExceptionFilter |
用於指定一個行為,這個被指定的行為處理某個行為方法或某個控制器里面拋出的異常 |
| 自定義 |
ActionFilterAttribute |
IActionFilter和IResultFilter |
用於進入行為之前或之后的處理或返回結果的之前或之后的處理 |
下面介紹的過濾器中,除了上面這幾種外,還多加一種過濾器OutputCache
1 授權過濾器Authorize
1.1 默認Authorize使用
現在在網上無論是要求身份驗證的地方多得是,發郵件,買東西,有時候就算吐個槽都要提示登錄的。這里的某些操作,就是要經過驗證授權才被允許。在MVC中可以利用Authorize來實現。例如一個簡單的修改密碼操作
[Authorize]
public ActionResult ChangePassword()
{
return View();
}
它需要用戶通過了授權才能進入到這個行為方法里面,否則硬去請求那個頁面的話,只會得到這個結果

如果要通過驗證,通過調用FormsAuthentication.SetAuthCookie方法來獲得授權,登陸的頁面如下
@model FilterTest.Models.LogInModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<div>
@using( Html.BeginForm()){
<div>
ID:@Html.TextBoxFor(m=>m.UserName)
<br />
Password:@Html.PasswordFor(m => m.Password)
<br />
<input type="submit" value="login" />
</div>
}
</div>
</body>
</html>
行為方法如下
[HttpPost]//這里用了謂詞過濾器,只處理POST的請求
public ActionResult Login(LogInModel login)
{
if (login.UserName == "admin" && login.Password == "123456")
{
FormsAuthentication.SetAuthCookie(login.UserName, false);
return Redirect("/Customer/ChangePassword");
}
return View();
}
當然有登錄也要有注銷,因為注銷是在登陸之后發生的,沒登陸成功也就沒有注銷,所以注銷的行為方法也要加上Authorize過濾器,注銷調用的是FormsAuthentication.SignOut方法,代碼如下
[Authorize]
public ActionResult LogOut()
{
FormsAuthentication.SignOut();
return Redirect("/Customer/Login");
}
1.2 自定義授權
我們不一定要用MVC默認的Authorize授權驗證規則,規則可以自己來定,自定義授權過濾器可以繼承AuthorizeAttribute這個類,這個類里面有兩個方法是要重寫的
- bool AuthorizeCore(HttpContextBase httpContext):這里主要是授權驗證的邏輯處理,返回true的則是通過授權,返回了false則不是。
- void HandleUnauthorizedRequest(AuthorizationContext filterContext):這個方法是處理授權失敗的事情。
這里就定義了一個比較騎呢的授權處理器,當請求的時候剛好是偶數分鍾的,就通過可以獲得授權,反之則不通過。當授權失敗的時候,就會跳轉到登陸頁面了。
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//return base.AuthorizeCore(httpContext);
return DateTime.Now.Minute % 2 == 0
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.HttpContext.Response.Redirect("/Customer/Login");
//base.HandleUnauthorizedRequest(filterContext);
}
}
然后用到一個行為方法上,
[MyAuthorize]
public ActionResult ShowDetail()
{
return View();
}
每當偶數分鍾的時候就可以訪問得到這個ShowDetail的視圖,否則就會跳到了登陸頁面了。
2 處理錯誤過濾器HandleError
2.1 默認HandleError使用
在往常的開發中,想到異常處理的馬上就會想到try/catch/finally語句塊。在MVC里面,萬一在行為方法里面拋出了什么異常的,而那個行為方法或者控制器有用上HandleError過濾器的,異常的信息都會在某一個視圖顯示出來,這個顯示異常信息的視圖默認是在Views/Shared/Error
這個HandleError的屬性如下
| 屬性名稱 |
類型 |
描述 |
| ExceptionType |
Type |
要處理的異常的類型,相當於Try/Catch語句塊里Catch捕捉的類型,如果這里不填的話則表明處理所有異常 |
| View |
String |
指定需要展示異常信息的視圖,只需要視圖名稱就可以了,這個視圖文件要放在Views/Shared文件夾里面 |
| Master |
String |
指定要使用的母版視圖的名稱 |
| Order |
Int |
指定過濾器被應用的順序,默認是-1,而且優先級最高的是-1 |
這個Order屬性其實不只這個HandleError過濾器有,其優先級規則跟其他過濾器的都是一樣。
下面則故意弄一個會拋異常的行為方法
[HandleError(ExceptionType = typeof(Exception))]
public ActionResult ThrowErrorLogin()
{
throw new Exception("this is ThrowErrorLogin Action Throw");
}
光是這樣還不夠,還要到web.config文件中的<system.web>一節中添加以下代碼
<customErrors mode="On" />
因為默認的開發模式中它是關閉的,要等到部署到服務器上面才會開啟,讓異常信息比較友好的用一個視圖展現。
像這里訪問ThrowErrorLogin視圖時,由於拋出了一次,就轉到了一個特定的視圖

在這里看到的異常視圖是這樣的,除了用這個建項目時默認生成的異常視圖之外,我們還可以自己定義異常視圖,視圖里面要用到的異常信息,可以通過@Model獲取,它是一個ExceptionInfo類型的實例,例如這里建了一個異常視圖如下
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>MyErrorPage</title>
</head>
<body>
<div>
<p>
There was a <b>@Model.Exception.GetType().Name</b>
while rendering <b>@Model.ControllerName</b>'s
<b>@Model.ActionName</b> action.
</p>
<p style="color:Red">
<b>@Model.Exception.Message</b>
</p>
<p>Stack trace:</p>
<pre style=" padding: 0px; color: rgb(0, 0, 255); line-height: 1.5 !important;">>@Model.Exception.StackTrace</pre>
</div>
</body>
</html>
它存放的路徑是~/Views/Shared里面,像上面的行為方法如果要用異常信息渲染到這個視圖上面,在控制器的處改成這樣就可以了
[HandleError(ExceptionType = typeof(Exception), View = "MyErrorPage")]

2.2 自定義錯誤異常處理
這里的錯誤處理過濾器也可以自己來定義,做法是繼承HandleErrorAttribute類,重寫void OnException(ExceptionContext filterContext)方法,這個方法調用是為了處理未處理的異常,例如
public override void OnException(ExceptionContext filterContext)
{
//base.OnException(filterContext);
if (!filterContext.ExceptionHandled &&
filterContext.Exception.Message == "this is ThrowErrorLogin Action Throw")
{
filterContext.ExceptionHandled=true;
filterContext.HttpContext.Response.Write("5洗ten No Problem<br/>" +
filterContext.Exception.ToString());
}
}
這里用到的傳入了一個ExceptionContext的對象,既可以從它那里獲得請求的信息,又可以獲取異常的信息,它部分屬性如下
| 屬性名稱 | 類型 | 描述 |
| ActionDescriptor | ActionDescriptor | 提供詳細的操作方法 |
| Result | ActionResult | 結果的操作方法,過濾器可以取消,要求將此屬性設置為一個非空值 |
| Exception | Exception | 未處理的異常 |
| ExceptionHandled | bool | 另一個過濾器,則返回true,如果有明顯的異常處理 |
這里的ExceptionHandler屬性要提一下的是,如果這個異常處理完的話,就把它設為true,那么即使有其他的錯誤處理器捕獲到這個異常,也可以通過ExceptionHandler屬性判斷這個異常是否經過了處理,以免重復處理一個異常錯誤而引發新的問題。
3 OutputCache過濾器
OutputCache過濾器用作緩存,節省用戶訪問應用程序的時間和資源,以提高用戶體驗,可這個我試驗試不出它的效果。留作筆記記錄一下。OutputCacheAttribute這個類有以下屬性
| 屬性名稱 |
類型 |
描述 |
| Duration |
int |
緩存的時間,以秒為單位,理論上緩存時間可以很長,但實際上當系統資源緊張時,緩存空間還是會被系統收回。 |
| VaryByParam |
string |
以哪個字段為標識來緩存數據,比如當“ID”字段變化時,需要改變緩存(仍可保留原來的緩存),那么應該設VaryByParam為"ID"。這里你可以設置以下幾個值: |
| Location |
OutputCacheLocation |
緩存數據放在何處。默認是Any,其他值分別是Client,Downstream,Server,None,ServerAndClient |
| NoStore |
bool |
用於決定是否阻止敏感信息的二級存儲。 |
例如一個OutputCache過濾器可以這樣使用
[OutputCache(Location= System.Web.UI.OutputCacheLocation.Client,Duration=60)]
public ActionResult Login()
{
return View();
}
或者有另外一種使用方式——使用配置文件,在<system.web>節點下添加以下設置
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="testCache" location="Client" duration="60"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
使用控制的時候就這樣
[OutputCache(CacheProfile="testCache")]
public ActionResult Login()
{
return View();
}
4 自定義過濾器
萬一前面介紹的過濾器也滿足不了需求,要在行為方法執行返回的前前后后定義自己的處理邏輯的話,這個自定義過濾器就應該能派上用場了。若要自定義一個過濾器,則要繼承ActionFilterAttribute類,這個類是一個抽象類,實現了IActionFilter和IResultFilter接口,主要通過重寫四個虛方法來達到在行為方法執行和返回的前后注入邏輯
| 方法 |
參數 |
描述 |
| OnActionExecuting |
ActionExecutingContext |
在行為方法執行前執行 |
| OnActionExecuted |
ActionExecutedContext |
在行為方法執行后執行 |
| OnResultExecuting |
ResultExecutingContext |
在行為方法返回前執行 |
| OnResultExecuted |
ResultExecutedContext |
在行為方法返回后執行 |
四個方法執行順序是OnActionExecuting——>OnActionExecuted——>OnResultExecuting——>OnResultExecuted。上面四個方法的參數都是繼承基ContollorContext類。例如下面定義了一個自定義的過濾器
public class MyCustomerFilterAttribute : ActionFilterAttribute
{
public string Message { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
filterContext.HttpContext.Response.Write(string.Format( "<br/> {0} Action finish Execute.....",Message));
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CheckMessage(filterContext);
filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action start Execute.....", Message));
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action finish Result.....", Message));
base.OnResultExecuted(filterContext);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action start Execute.....", Message));
base.OnResultExecuting(filterContext);
}
private void CheckMessage(ActionExecutingContext filterContext)
{
if(string.IsNullOrEmpty( Message)||string.IsNullOrWhiteSpace(Message))
Message = filterContext.Controller.GetType().Name + "'s " + filterContext.ActionDescriptor.ActionName;
}
}
使用它的行為方法定義如下
[MyCustomerFilter]
public ActionResult CustomerFilterTest()
{
Response.Write("<br/>Invking CustomerFilterTest Action");
return View();
}
執行結果如下

這個就證明了上面說的順序。
當控制器也使用上這過濾器時,而行為方法不使用時,結果如下

如果控制器和行為方法都使用了過濾器,理論上是顯示上面兩個結果的有機結合。但實際不然,因為在定義過濾器的時候還少了一個特性:[AttributeUsage(AttributeTargets.All, AllowMultiple = true)],把這個加在MyCustomerFilterAttribute就行了。
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]//多次調用
public class MyCustomerFilterAttribute : ActionFilterAttribute
{
……
}

由這幅圖可以看出,同一個過濾器分別用在了控制器和行為方法中,執行同一個方法時都會有先后順序,如果按默認值(不設Order的情況下),一般的順序是由最外層到最里層,就是“全局”——>“控制器”——>“行為方法”;而特別的就是錯誤處理的過濾器,由於異常是由里往外拋的,所以它的順序剛好也反過來:“行為方法”——>“控制器”——>“全局”。
既然這里有提到全局的過濾器,那么全局的過濾器是在Global.asax文件里面的RegisterGlobalFilters(GlobalFilterCollection filters)中設置的
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new MyFilters.MyCustomerFilterAttribute() { Message="Global"});//全局過濾器
}

