过滤器 (Filter) 是 MVC 的一个重要特征,它提供了非常灵活的扩展和解耦机制。P4 版的过滤器总算像那么回事了,同时做了更深度的划分,使得开发人员可以控制更多的细节和流程。
1. IActionFilter
主要用来干预 Action 的执行,我们可以通过上下文对象修改请求参数或者取消 Action 的执行。
{
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
默认实现是 ActionFilterAttribute,这也是我们开发自定义过滤器通常所用的基类。
{
public virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext)
{
}
// …
}
ActionExecutingContext 有个很重要的属性 Cancel,通过它我们可以取消 Action 和后续 Filter 的执行。
{
// Methods
public ActionExecutingContext(ControllerContext controllerContext, MethodInfo actionMethod,
IDictionary<string, object> actionParameters);
// Properties
public MethodInfo ActionMethod { get; private set; }
public IDictionary<string, object> ActionParameters { get; private set; }
public bool Cancel { get; set; }
public ActionResult Result { get; set; }
}
public class ActionExecutedContext : ControllerContext
{
// Methods
public ActionExecutedContext(ControllerContext controllerContext, MethodInfo actionMethod,
bool canceled, Exception exception);
// Properties
public MethodInfo ActionMethod { get; private set; }
public bool Canceled { get; private set; }
public Exception Exception { get; private set; }
public bool ExceptionHandled { get; set; }
public ActionResult Result { get; set; }
}
举例说明一下。
{
[Filter1(Order = 1)]
[Filter2(Order = 2)]
[Filter3(Order = 3)]
public ActionResult Index()
{
}
}
当我们在 Filter2.OnActionExecuting 中执行取消操作时,那么实际输出效果应该就是下面这样子。
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Cancel = true;
}
}
输出:
TestController.OnActionExecuting
Filter1.OnActionExecuting
Filter2.OnActionExecuting
Filter1.OnActionExecuted
TestController.OnActionExecuted
我 们发现 Action 和排在后面的 Filter3 没有被调用,同时 Filter2 的 OnActionExecuted 也没有被执行。由于 Filter.Order 最小值是 -1,而 Controller.Order = -1,因此默认情况下 Controller Filter 总是被调用执行。
2. IResultFilter
可以利用该类型过滤器修改输出结果,诸如视图引擎和输出数据等等。
{
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
其默认实现同样是 ActionFilterAttribute。
{
// …
public virtual void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
在 ResultExecutingContext 中也有个 Cancel 属性,通过它我们可以阻止 View 输出和后续 Filter 执行。
{
// Methods
public ResultExecutingContext(ControllerContext controllerContext, ActionResult result);
// Properties
public bool Cancel { get; set; }
public ActionResult Result { get; set; }
}
public class ResultExecutedContext : ControllerContext
{
// Methods
public ResultExecutedContext(ControllerContext controllerContext, ActionResult result,
bool canceled, Exception exception);
// Properties
public bool Canceled { get; private set; }
public Exception Exception { get; private set; }
public bool ExceptionHandled { get; set; }
public ActionResult Result { get; private set; }
}
同样在上文例子中的 Filter2.OnResultExecuting 中设置取消操作。
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Cancel = true;
}
}
输出:
TestController.OnResultExecuting
Filter1.OnResultExecuting
Filter2.OnResultExecuting
Filter1.OnResultExecuted
TestController.OnResultExecuted
Action 返回的 View(ActionResult) 以及 Filter3、Filter2.OnResultExecuted 都没有被执行。
3. IAuthorizationFilter
这是新增的过滤器类型,它拥有最先执行的特权,用来实现登录验证操作。
{
void OnAuthorization(AuthorizationContext filterContext);
}
默认实现是 AuthorizeAttribute。
{
// Methods
public AuthorizeAttribute();
public void OnAuthorization(AuthorizationContext filterContext);
// Properties
public string Roles { get; set; }
public string Users { get; set; }
}
AuthorizeAttribute 通过对 HttpContext.User 进行一系列的判断操作,然后输出一个新增的 ActionResult —— HttpUnauthorizedResult,这算是其中最有价值的代码了。
{
public override void ExecuteResult(ControllerContext context)
{
// …
// 401 is the HTTP status code for unauthorized access – setting this
// will cause the active authentication module to execute its default
// unauthorized handler
context.HttpContext.Response.StatusCode = 401;
}
}
上下文对象中也有个 Cancel,通过它可以告诉 ActionInvoker 停止后面的调用操作,跳转到登录页。
{
// Methods
public AuthorizationContext(ControllerContext controllerContext, MethodInfo actionMethod);
// Properties
public MethodInfo ActionMethod { get; private set; }
public bool Cancel { get; set; }
public ActionResult Result { get; set; }
}
详情可参考下面这些代码。
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// …
IPrincipal user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
filterContext.Cancel = true;
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (!String.IsNullOrEmpty(Users))
{
IEnumerable<string> validNames = SplitString(Users);
bool wasMatch = validNames.Any(name => String.Equals(name, user.Identity.Name,
StringComparison.OrdinalIgnoreCase));
if (!wasMatch)
{
filterContext.Cancel = true;
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
if (!String.IsNullOrEmpty(Roles))
{
IEnumerable<string> validRoles = SplitString(Roles);
bool wasMatch = validRoles.Any(role => user.IsInRole(role));
if (!wasMatch)
{
filterContext.Cancel = true;
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
public class ControllerActionInvoker
{
public virtual bool InvokeAction(string actionName, IDictionary<string, object> values)
{
// …
AuthorizationContext authContext = InvokeAuthorizationFilters(methodInfo,
filterInfo.AuthorizationFilters);
if (authContext.Cancel)
{
// not authorized, so don't execute the action method or its filters
InvokeActionResult(authContext.Result ?? EmptyResult.Instance);
}
// …
}
protected virtual AuthorizationContext InvokeAuthorizationFilters(…)
{
// …
AuthorizationContext context = new AuthorizationContext(ControllerContext, methodInfo);
foreach (IAuthorizationFilter filter in filters)
{
filter.OnAuthorization(context);
// short-circuit evaluation
if (context.Cancel)
{
break;
}
}
return context;
}
}
ControllerActionInvoker 通过 InvokeAuthorizationFilters() 调用 AuthorizeAttribute,然后依次判断登录和角色信息。如果条件不符,则设置取消操作并返回一个 HttpUnauthorizedResult,这个 ActionResult 被 InvokeAciton 所激活,因此才有了 "context.HttpContext.Response.StatusCode = 401" 跳转。
4. IExceptionFilter
用于捕获 Filter 和 Action 执行异常,以便实现自定义错误页面。
{
void OnException(ExceptionContext filterContext);
}
默认实现是 HandleErrorAttribute。
{
// Methods
public HandleErrorAttribute();
void OnException(ExceptionContext filterContext);
// Properties
public Type ExceptionType { get; set; }
public string View { get; set; }
}
从上下文中我们可以获取具体的异常信息,可以使用 ExceptionHandled 来阻止后续 HandleErrorAttribute 执行,还可以设置一个 ActionResult 来显示错误页面。
{
// Methods
public ExceptionContext(ControllerContext controllerContext, Exception exception);
// Properties
public Exception Exception { get; private set; }
public bool ExceptionHandled { get; set; }
public ActionResult Result { get; set; }
}
public sealed class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
// …
if (controller == null || filterContext.ExceptionHandled)
{
return;
}
// …
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult()
{
TempData = controller.TempData,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
ViewEngine = controller.ViewEngine,
ViewName = View,
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
}
}
ControllerActionInvoker 使用 try…catch 来捕获执行异常,并通过 InvokeExceptionFilters 完成 OnException 调用。
{
public virtual bool InvokeAction(string actionName, IDictionary<string, object> values)
{
// …
try
{
// …
}
catch (Exception ex)
{
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters(ex, filterInfo.ExceptionFilters);
if (!exceptionContext.ExceptionHandled)
{
throw;
}
InvokeActionResult(exceptionContext.Result);
}
// …
}
protected virtual ExceptionContext InvokeExceptionFilters(exception, filters)
{
// …
ExceptionContext context = new ExceptionContext(ControllerContext, exception);
foreach (IExceptionFilter filter in filters)
{
filter.OnException(context);
}
return context;
}
}
附: Controller 实现了上述所有的过滤器类型。
IActionFilter,
IAuthorizationFilter,
IController,
IDisposable,
IExceptionFilter,
IResultFilter
{
}
—————–
本文仅分析一些调用细节,有关使用方法可参考 《ASP.NET MVC Preview 4 Release (Part 1) 》 (中文版)。