[MVC]ASP.NET MVC Preview 4 分析 - 2. Filter

过滤器 (Filter) 是 MVC 的一个重要特征,它提供了非常灵活的扩展和解耦机制。P4 版的过滤器总算像那么回事了,同时做了更深度的划分,使得开发人员可以控制更多的细节和流程。
1. IActionFilter
主要用来干预 Action 的执行,我们可以通过上下文对象修改请求参数或者取消 Action 的执行。

public interface IActionFilter
{
  void OnActionExecuting(ActionExecutingContext filterContext);
  void OnActionExecuted(ActionExecutedContext filterContext);
}

默认实现是 ActionFilterAttribute,这也是我们开发自定义过滤器通常所用的基类。

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
  public virtual void OnActionExecuting(ActionExecutingContext filterContext)
  {
  }
  public virtual void OnActionExecuted(ActionExecutedContext filterContext)
  {
  }
  // …
}

ActionExecutingContext 有个很重要的属性 Cancel,通过它我们可以取消 Action 和后续 Filter 的执行。

public class ActionExecutingContext : ControllerContext
{
  // 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; }
}

举例说明一下。

public class TestController : Controller
{
  [Filter1(Order = 1)]
  [Filter2(Order = 2)]
  [Filter3(Order = 3)]
  public ActionResult Index()
  {
  }
}

当我们在 Filter2.OnActionExecuting 中执行取消操作时,那么实际输出效果应该就是下面这样子。

class Filter2 : Filter1
{
  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
可以利用该类型过滤器修改输出结果,诸如视图引擎和输出数据等等。

public interface IResultFilter
{
  void OnResultExecuting(ResultExecutingContext filterContext);
  void OnResultExecuted(ResultExecutedContext filterContext);
}

其默认实现同样是 ActionFilterAttribute。

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
  // …
  public virtual void OnResultExecuting(ResultExecutingContext filterContext)
  {
  }
  public virtual void OnResultExecuted(ResultExecutedContext filterContext)
  {
  }
}

在 ResultExecutingContext 中也有个 Cancel 属性,通过它我们可以阻止 View 输出和后续 Filter 执行。

public class ResultExecutingContext : ControllerContext
{
  // 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 中设置取消操作。

class Filter2 : Filter1
{
  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
这是新增的过滤器类型,它拥有最先执行的特权,用来实现登录验证操作。

public interface IAuthorizationFilter
{
  void OnAuthorization(AuthorizationContext filterContext);
}

默认实现是 AuthorizeAttribute。

public sealed class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
  // 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 class HttpUnauthorizedResult : ActionResult
{
  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 停止后面的调用操作,跳转到登录页。

public class AuthorizationContext : ControllerContext
{
  // 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 sealed class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
  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 执行异常,以便实现自定义错误页面。

public interface IExceptionFilter
{
  void OnException(ExceptionContext filterContext);
}

默认实现是 HandleErrorAttribute。

public sealed class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
  // Methods
  public HandleErrorAttribute();
  void OnException(ExceptionContext filterContext);
  // Properties
  public Type ExceptionType { get; set; }
  public string View { get; set; }
}

从上下文中我们可以获取具体的异常信息,可以使用 ExceptionHandled 来阻止后续 HandleErrorAttribute 执行,还可以设置一个 ActionResult 来显示错误页面。

public class ExceptionContext : ControllerContext
{
  // 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 class ControllerActionInvoker
{
  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 实现了上述所有的过滤器类型。

public abstract class Controller :
  IActionFilter,
  IAuthorizationFilter,
  IController,
  IDisposable,
  IExceptionFilter,
  IResultFilter
{
}

—————–
本文仅分析一些调用细节,有关使用方法可参考 《ASP.NET MVC Preview 4 Release (Part 1) 》 (中文版)。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏