理解Filter
Filter的应用场景
使用ASP.NET MVC框架进行开发时,是否恰当地使用Filter,是判断一个程序员逼格高低的一个重要因素。请求某个WEB页面或提交表单时,是否出现过那个经典的黄色错误页面?在调用某个返回JsonResult的Action时,是否返回了500错误?在判断用户是否登录或是否具有特定权限时,同样的代码是否在多个Action方法中到处都是?使用Filter,可以以更加优雅的方式来处理以上的问题。
Filter可以在处理Web请求时,以横切的方式在特定的环节注入一些逻辑,这些逻辑在一个地方进行编写,并且非常轻松地在多个地方进行调用。
四种基础类型的Filter
MVC框架提供了四种基础类型的Filter,如下表:
类型 | 接口 | 默认实现类 | 描述 |
---|---|---|---|
Authorization | IAuthorizationFilter | AuthorizeAttribute | 最先运行,先于其它Filter和Action方法 |
Exception | IExceptionFilter | HandleErrorAttribute | 当Action方法或其他Filter运行的过程中抛出异常时运行 |
Action | IActionFilter | ActionFilterAttribute | 在Action方法运行前或运行后执行 |
Result | IResultFilter | ActionFilterAttribute | 在Action方法返回前或返回后执行 |
将Filter应用到Controller或Action
将Filter应用到Controller或Action的方式很简单,下面的代码是把一个Filter应用到一个Action方法中
public class AdminController : Controller {
[Authorize]
public ViewResult Index() {
// ...其它代码
}
[Authorize]
public ViewResult Create() {
// ...其它代码
}
}
将Filter应用到整个Controller
[Authorize]
public class AdminController : Controller {
public ViewResult Index() {
// ...其它代码
}
public ViewResult Create() {
// ...其它代码
}
}
使用Authorization Filter来验证用户是否登录
在处理URL请求时,Authorization Filter先与Action方法和其他的Filter运行,用来判断当前用户是否已经登录,如果未登录,则跳转到登录页面进行登录。我们可以通过实现IAuthorizationFilter接口来完成验证,也可以直接使用MVC框架内置的AuthorizeAttribute类来完成验证。但是,为了兼顾安全性和灵活性,本篇文章通过继承AuthorizeAttribute类并重写AuthorizeCore方法来完成。下面是个最简单的例子
public class CustomAuthAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Request.IsLocal || Session["username"] + "" != "")
{
return true;
}
else
{
return false;
}
}
}
将这个自定义的Authorization Filter应用于Controller
[CustomAuth]
public class AdminController : Controller {
public ViewResult Index() {
// ...其它代码
}
public ViewResult Create() {
// ...其它代码
}
}
使用Authorization Filter也可以用来做特定的权限验证,方法与自定义的Authorization Filter一样。
处理验证失败的返回结果
如果验证失败,可以设置一个跳转的路径。有时候,返回ViewResult和返回JsonResult的Action的返回方式还不一样,下面的代码演示了通过重写HandleUnauthorizedRequest方法来分别设置返回结果
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult()
{
//看具体情况来返回数据
Data = new { Succeed = false, Message = "请刷新页面后重新登录"}
};
}
else
{
filterContext.Result = new RedirectResult(
urlHelper.Action("NeedLogin", "Authorize",
new { ReturnUrl = filterContext.HttpContext.Request.Url.ToString() }));
}
}
使用Exception Filter来处理异常
当Action方法抛出异常时,或者其它的Filter执行时抛出异常时,Exception Filter将会被执行。Exception Filter的主要目的有两个,一个是记录错误日志,另一个是重定向到一个友好的错误页面,而不是现实经典的黄色错误页面。
自定义一个Exception Filter
自定义一个Exception Filter需要实现IExceptionFilter接口,定义如下:
namespace System.Web.Mvc {
public interface IExceptionFilter {
void OnException(ExceptionContext filterContext);
}
}
当异常发生时,OnException方法被调用,该方法的参数是一个ExceptionContext类型的对象,ExceptionContext类继承自ControllerContext类,我们可以直接使用ControllerContext类中的很多对象,如:Controller、HttpContext、RequestContext、IsChildAction、RouteData等。同时,ExceptionContext类还定义了以下属性供我们使用:
属性名 | 类型 | 描述 |
---|---|---|
ActionDescriptor | ActionDescriptor | 提供抛出异常的Action方法的一些信息 |
Result | ActionResult | 出现异常后,返回给用户的响应结果 |
Exception | Exception | 被抛出的异常 |
ExceptionHandled | bool | 该异常是否被处理过 |
如果一个Exception Filter处理了某个异常,就把ExceptionHandled属性设置为True。值得注意的是,当一个Action方法抛出异常时,跟这个Action关联的所有Exception Filter类都会被执行,无论ExceptionHandled是否为True。所以,建议在自定义一个Exception Filter类的时候,要首先判断一些ExceptionHandled属性是否为True。
在自定义的Exception Filter中,我们可以记录错误日志,并且指定一个返回页面。
public class CustomExceptionAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled &&
filterContext.Exception is ArgumentOutOfRangeException)
{
// log the error
// ...
// 指定返回结果
filterContext.Result
= new RedirectResult("~/Content/RangeErrorPage.html");
filterContext.ExceptionHandled = true;
}
}
}
也可以指定一个View,并且想这个View传递一些数据
public class CustomExceptionAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled &&
filterContext.Exception is ArgumentOutOfRangeException)
{
// log the error
// ...
// 指定返回结果
filterContext.Result = new ViewResult {
ViewName = "CustomError",
ViewData = new ViewDataDictionary<Object>(myObj)
};
filterContext.ExceptionHandled = true;
}
}
}
这里的名为CustomError的View,是一个强类型视图,类型为Object(可以指定为任何类型)
一定要注意,在CustomError这个View中使用动态数据时不要产生异常。
使用内置的Exception Filter来处理异常
上面演示了如何使用自定义的Exception Filter来处理异常,这在开发中用的不是太多,但有助于我们了解Exception Filter的工作方式。HandleErrorAttribute类是MVC框架中内置的用来处理异常的Filter,它的两个属性比较重要,一个是ExceptionType,类型为System.Type,它可以指定要处理的异常类型,如果不指定的话,默认为System.Exception类,即,处理所有异常类型。另一个比较重要的属性是View,类型为System.String,可以通过这个属性指定出现异常后返回的视图地址。默认为/Views/Shared/Error.cshtml 或 /Views/Shared/Error.cshtml。
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "CustomError")]
[Authorize]
public class AdminController : Controller {
public ViewResult Index() {
// ...其它代码
}
public ViewResult Create() {
// ...其它代码
}
}
默认情况下,HandleErrorAttribute类并不能工作,需要激活一下,激活的方法是,在web.config文件中,System.Web节点下添加一个节点customErrors,将mode的属性设置为On
<system.Web>
<customErrors mode="On" defaultRedirect="/Views/Shared/Error.cshtml"/>
</system.Web>
处理Action Filter和Result Filter
MVC框架为我们提供了ActionFilterAttribute类,定义如下:
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)
{
}
}
OnActionExecuting方法在Action调用之前执行,OnActionExecuted方法在Action调用之后执行,OnResultExecuting在Action返回之前调用,OnResultExecuted在Action返回之后调用。我们可以定义一个类继承自ActionFilterAttribute ,然后重写某个方法。应用场景一般用于记录特定Action的活动日志。
Filter的其它特性
全局Filter
我们可以通过下面的方式注册一个全局的Filter,应用于所有的Controller和所有的Action。向App_Start/FilterConfig.cs中的RegisterGlobalFilters方法添加一个Filter对象
using System.Web;
using System.Web.Mvc;
using Filters.Infrastructure;
namespace Filters {
public class FilterConfig {
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
//可以添加其它Filter对象
}
}
}
指定同类型的Filter类的执行顺序
如果为一个Action绑定了多个Action Filter,那个这些Filter的调用顺序是不确定的,我们可以指定这个顺序,通过设置Order属性为一个int值。Order属性定义在FilterAttribute类中,可以在所有的Filter中使用
[SimpleMessage(Message="A", Order=2)]
[SimpleMessage(Message="B", Order=1)]
public ActionResult Index() {
Response.Write("Action method is running");
return View();
}