ASP.NET MVC 4 (三) 過濾器


先來看看一個例子演示過濾器有什么用:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM