天天看點

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。