先來看看一個例子示範過濾器有什麼用:
public class AdminController : Controller {
// ... instance variables and constructor
public ViewResult Index() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
public ViewResult Create() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
...
AdminController控制器的衆多Action中我們都需要判定目前驗證使用者,這裡有很多重複的代碼,我們可以簡化為:
[Authorize]
public class AdminController : Controller {
// ... instance variables and constructor
public ViewResult Index() {
// ...rest of action method
}
public ViewResult Create() {
// ...rest of action method
}
...
Authorize特性類AuthorizeAttribute就稱作MVC的Filter,它在橫向為MVC架構擴充功能,讓我們可以更友善的處理日志、授權、緩存等而不影響縱向主體功能。
MVC常用的濾器類型:
- Authorization:實作IAuthorizationFilter接口,預設實作類AuthorizeAttribute,在調用Action方法前首先處理認證資訊。
- Action:實作IActionFilter接口,預設實作類ActionFilterAttribute,在運作Action前後調用實作一些額外的動作。
- Result:實作IResultFilter接口,預設實作類ActionFilterAttribute,在action result運作前後調用實作額外的動作。
- Exception:實作IExceptionFilter接口,預設實作類HandleErrorAttribute,僅在其他過濾器或者action方法或者action result抛出異常時調用。
過濾器可以應用在整個控制器類上,也可以單對某個Action方法,當然也可以是同時應用多個過濾器:
[Authorize(Roles="trader")] // applies to all actions
public class ExampleController : Controller {
[ShowMessage] // applies to just this action
[OutputCache(Duration=60)] // applies to just this action
public ActionResult Index() {
// ... action method body
}
}
Authorization過濾器
Authorization過濾器用于控制僅授權的使用者可以調用某個Action方法,它必須實作IAuthorizationFilter接口:
namespace System.Web.Mvc {
public interface IAuthorizationFilter {
void OnAuthorization(AuthorizationContext filterContext);
}
}
處理安全方面的代碼必須謹慎全面,一般我們不要直接實作接口IAuthorizationFilter,而從AuthorizeAttribute擴充自定義類:
public class CustomAuthAttribute : AuthorizeAttribute {
private bool localAllowed;
public CustomAuthAttribute(bool allowedParam) {
localAllowed = allowedParam;
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext.Request.IsLocal) {
return localAllowed;
} else {
return true;
}
}
}
這裡定義了CustomAuthAttribute過濾器,如果請求來自于非本機總是允許,如果是本機請求則視傳入參數allowedParam而定:
public class HomeController : Controller {
[CustomAuth(false)]
public string Index() {
return "This is the Index action on the Home controller";
}
}
預設實作AuthorizeAttribute足夠應付多數授權功能限制,我們可以傳入參數限制通路的使用者或者角色:
[Authorize(Users = "adam, steve, jacqui", Roles = "admin")]
需要注意的是AuthorizeAttribute僅僅負責授權,而使用者的認證則依賴于ASP.NET的認證機制。
Exception過濾器
Exception過濾器需要實作IExceptionFilter接口:
namespace System.Web.Mvc {
public interface IExceptionFilter {
void OnException(ExceptionContext filterContext);
}
}
從filterContext我們可以擷取很多相關資訊,比如Controller、HttpContext、IsChildAction、RouteData、Result、Exception、ExceptionHandled等。異常的具體資訊我們可以從Exception擷取,我們設定ExceptionHandled為true報告異常已經處理,在設定為true之前最好檢查異常是否已經被action方法上其他的過濾器處理,以避免重複的錯誤糾正動作。如果異常未被處理(ExceptionHandled!=true),MVC架構使用預設的異常處理程式顯示ASP.NET的黃屏錯誤頁面。
我們可以建立自定義的異常過濾器:
public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
public void OnException(ExceptionContext filterContext) {
if (!filterContext.ExceptionHandled &&
filterContext.Exception is ArgumentOutOfRangeException) {
int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue);
//filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html");
filterContext.Result = new ViewResult {
ViewName = "RangeError",
ViewData = new ViewDataDictionary<int>(val)
};
filterContext.ExceptionHandled = true;
}
}
}
注意這裡RangeExceptionAttribute除了實作IExceptionFilter接口還從FilterAttribute繼承,這是因為MVC的過濾器類還必須實作IMvcFilter接口,這裡我們沒有直接實作IMvcFilter接口,改為從預設封裝類FilterAttribute繼承。如下使用這個自定義過濾器:
...
[RangeException]
public string RangeTest(int id) {
if (id > 100) {
return String.Format("The id value is: {0}", id);
} else {
throw new ArgumentOutOfRangeException("id");
}
}
...
在發生異常時過濾器會将我們重定向到RangeError視圖。
通常我們不需要建立自己的異常處理器,而使用MVC提供的預設過濾器HandleErrorAttribute:
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError",Master="")]
屬性ExceptionType指定要處理的異常類型,如果不指定則處理所有異常類型;view指定錯誤時要渲染的視;master指定所用的頁面布局。
在使用HandleErrorAttribute過濾前我們必須在Web.config的<System.Web>一節啟用CusomErrors:
<customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
預設customErros為RemoteOnly,defaultRedirect指定一個預設的錯誤頁面。
Action過濾器
Action過濾器必須實作IActionFilter接口:
namespace System.Web.Mvc {
public interface IActionFilter {
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
}
OnActionExecuting()在調用action方法前調用,OnActionExecuted()則在調用action方法後調用。
OnActionExecuting的一個實作例子:
public class CustomActionAttribute : FilterAttribute, IActionFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Request.IsLocal) {
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
// not yet implemented
}
}
這裡我們僅實作了OnActionExecuting方法,在從本地請求任何應用此過濾器的action方法時傳回404錯誤。
OnActionExecuted的一個例子:
public class ProfileActionAttribute : FilterAttribute, IActionFilter {
private Stopwatch timer;
public void OnActionExecuting(ActionExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
timer.Stop();
if (filterContext.Exception == null) {
filterContext.HttpContext.Response.Write(
string.Format("<div>profile result method elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
}
在調用action方法前我們啟動計時器,在action方法調用後停止計時器并輸出計時器所計action方法運作時長。
Result過濾器
Result過濾器實作IResultFilter接口:
namespace System.Web.Mvc {
public interface IResultFilter {
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
}
Result過濾器操作的是action方法傳回結果,有意思的是即使action方法傳回void所應用的Result過濾器也會動作。
類似上面的ProfileAction過濾器我們可以對Result運作計時:
public class ProfileResultAttribute : FilterAttribute, IResultFilter {
private Stopwatch timer;
public void OnResultExecuting(ResultExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("<div>Result elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
内建的Result過濾器類ActionFilterAttribute
MVC為我們提供了ActionFilterAttribute類,它同時實作了action和result過濾器接口:
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter,
IResultFilter{
public virtual void OnActionExecuting(ActionExecutingContext filterContext) {
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext) {
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext) {
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext) {
}
}
}
我們隻需要繼承該類并重載需要的方法,比如:
public class ProfileAllAttribute : ActionFilterAttribute {
private Stopwatch timer;
public override void OnActionExecuting(ActionExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("<div>Total elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
Controller類的過濾器支援
MVC的Controller類内部實作了IAuthorizationFilter、IActionFilter、IResultFilter、IExceptionFilte四個接口,并提供OnXXX的虛函數供調用,比如:
public class HomeController : Controller {
private Stopwatch timer;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("<div>Total elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
...
全局過濾器
全局過濾器應用于應用程式内所有控制器的所有action方法,我們在App_Start/FilterConfig.cs可以注冊全局過濾器:
public class FilterConfig {
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
filters.Add(new ProfileAllAttribute());
}
}
HandleErrorAttribute是VS為我們預設添加的,用于未處理異常錯誤時顯示/Views/Shared/Error.cshtml頁面;ProfileAllAttribute則是我們添加的自定義過濾器,運作任何action方法時都會調用這個過濾器。
其他MVC内建過濾器
MVC架構還内建提供以下過濾器:
- RequireHttps:訓示必須以HTTPS通路action方法,僅用于HTTP get方法
- OutputCache:緩存action方法的結果
- ValidateInput和ValidationAntiForgeryToken:安全授權相關的過濾器
- AsnycTimeOut和NoAsyncTimeout:用于異步控制器
- ChildActionOnlyAttribute:用于授權Html.Action或者Html.RenderAction調用子action方法
這些過濾器的使用方法可以參見MSDN。
過濾器的運作順序
過濾器的運作按先後順序是:authorization過濾器、action過濾器、result過濾器,期間任何時刻發生未處理異常調用異常處理器。這是針對不同類型的過濾器,但是如果所應用的是同一類的過濾器呢?MVC預設并不保證同類型過濾器的調用程式,也就是說很可能并非按照出現在代碼中的先後程式來調用過濾器,但是我們可以顯式的指定它們的調用順序:
...
[SimpleMessage(Message="A", Order=2)]
[SimpleMessage(Message="B", Order=1)]
public ActionResult Index() {
Response.Write("Action method is running");
return View();
}
...
這裡SimpleMessage是一個action過濾器,通過order我們指定它們的運作順序: B->OnActionExecuting、A->OnActionExecuting、A->OnActionExecuted、B->OnActionExecuted。注意A的OnActionExecuted先于B的OnActionExecuted,和OnActionExecuting正好相反,這是無法改變的。在不指定order時,MVC内部指定為-1。另外如果同類型過濾器指定相同的order比如都是1,還要根據在哪裡應用的過濾器來分先後執行:首先執行的是全局過濾器、然後是控制器類上、最後才是action方法;例外的是exception過濾器,它的順序正好與此相反!
以上為對《Apress Pro ASP.NET MVC 4》第四版相關内容的總結,不詳之處參見原版 http://www.apress.com/9781430242369。