先來看看一個例子演示過濾器有什么用:
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。