ASP.NET MVC 4.0的Action Filter

有时候你想在调用action方法之前或者action方法之后处理一些逻辑,为了支持这个,ASP.NET MVC允许你自定义创建action过滤器。Action过滤器是自定义的Attributes,用来标记添加Action方法之前或者Action方法之后的行为到控制器类中的Action方法中。

什么情况下可能会用到action过滤的地方

  • 日志,异常处理
  • 身份验证和授权 - 限制用户的访问
  • 输出缓存 - 保存一个Action的结果
  • 网络爬虫的过滤
  • 本地化
  • 动态Action - 将一个Action注入到控制器中

ASP.NET MVC 4.0 Framework支持四种不同类型的Filter

  1. Authorization filters – 实现IAuthorizationFilter接口的属性.
  2. Action filters – 实现IActionFilter接口的属性.
  3. Result filters – 实现IResultFilter接口的属性.
  4. Exception filters – 实现IExceptionFilter接口的属性.

ASP.NET 内置的Filter默认的执行顺序按上面的列表中顺序进行。如验证(authorization)Filter永远都是最开始执行的,异常(exception)Filter永远都是最后执行的,当然你也可以根据需要通过Order属性设定过滤器执行的顺序。

Action过滤器的执行顺序

每一个Action过滤器都有一个 Order 属性,用来决定Action过滤器在该范围内的执行顺序。Order属性必需是0(默认值)或者更大的整数值。省略Order属性则会给该过滤器的Order值为 -1, 表明为指明顺序。任何一个在同一范围的Action过滤器Order设为 -1 的都将按不确定的顺序执行,单在此之前过滤器有一个特定的顺序(注:下面会说到).

当设置Order属性的值的时候,必需指定一个唯一的值。如果两个或者更多的Action过滤器具有相同的Order属性值,将会抛出一个异常。

1 [Filter1(Order = 2)]
2 [Filter2(Order = 3)]
3 [Filter3(Order = 1)]
4 public ActionResult Index()
5 {
6    return View();
7 }

Filter的执行顺序为:Filter3 => Filter1 => Filter2.

用户自定义ActionFilter

为了让用户更简单的创建一个自定义action filter,ASP.NET MVC 4.0 Framework提供了一个基类ActionFilterAttribute,这个类实现了IActionFilter和IResultFilter接口,并且继承了Filter类。

这里的术语并不完全一致,从技术上说,这个类继承ActionFitlerAttribute,并且同时实现了action filter和result filter接口,但是从宽松意义上说,在ASP.NET MVC Framework中,任何实现filter的类型都是action filter。

ActionFilterAttribute类有以下的方法可以重写:

  • OnActionExecuting – 在controller action执行之前调用
  • OnActionExecuted – 在controller action执行之后调用
  • OnResultExecuting – 在controller action result执行之前调用
  • OnResultExecuted – 在controller action result执行之后调用

以下是这几个方法的执行顺序:

OnActionExecuting()-> Action execute & return View() ->OnActionExecuted() ->OnResultExecuting() -> Render View() ->OnResultExecuted()

举个例子:检查登录是否为管理员身份

 1     public class AdminFilter : ActionFilterAttribute //自定义AdminFilter继承ActionFilterAttritube
 2     {
 3         public override void OnActionExecuting(ActionExecutingContext filterContext)
 4         {
 5             if (!Convert.ToBoolean(filterContext.HttpContext.Session["IsAdmin"]))
 6             {
 7                 //如果不是管理员,提示如下content
 8                 filterContext.Result = new ContentResult()
 9                 {
10                     Content = "Unauthorized to access specified resource."
11                 };
12             }
13         }
14     }
1         //引用自定义过滤器
2         [AdminFilter]
3         public ActionResult AddNew()
4         {
5             CreateEmployeeViewModel cvm = new CreateEmployeeViewModel();
6             return View("CreateEmployee",cvm);
7         }

用户自定义带参数ActionFilter

 1     public class AdminFilter : ActionFilterAttribute
 2     {
 3         public string Message { get; set; } //增加message属性
 4         public override void OnActionExecuting(ActionExecutingContext filterContext)
 5         {
 6             if (!Convert.ToBoolean(filterContext.HttpContext.Session["IsAdmin"]))
 7             {
 8                 //直接提示content内容,不会执行action
 9                 filterContext.Result = new ContentResult()
10                 {
11                     //action调用过滤器时,得到message值
12                     Content = Message + "Unauthorized to access specified resource."
13                 };
14             }
15         }
16     }        
 1         [AdminFilter(Message = "GetAddNewLink")] //传入message参数
 2         public ActionResult GetAddNewLink()
 3         {
 4             if(Convert.ToBoolean(Session["IsAdmin"]))
 5             {
 6                 return PartialView("AddNewLink");
 7             }
 8             else
 9             {
10                 return new EmptyResult();
11             }
12             
13         }

自定义过滤器加入到全局过滤器

有时我们想有些公共的方法需要每个Action都执行,但是又不想再每一个Controller上都打上Action标签,怎么办?幸好Asp.Net 从MVC3带来了一个美好的东西,全局Filter。而怎么注册全局Filter呢?答案就在Global.asax中。让我们看以下代码,我是如何将上面我们定义的AdminFilter注册到全局Filter中。

App_Start /FilterConfig.cs

1     public class FilterConfig
2     {
3         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
4         {
5             filters.Add(new HandleErrorAttribute());
6             filters.Add(new AdminFilter() { Message = "global" });
7         }
8     }

补充说明:

如果标签打到Controller上的话,AdminFilter将作用到Controller下的所有的Action。

  1.默认情况下Action上打了某个自定义标签后,虽然在Controller上也打上了此标签,但它只有Action上的标签起作用了。

  2.如果Action没有打上该标签,那么Controller上的标签便会被执行。

如果想让Action上的标签执行一次,然后Controller上的标签也执行一次,那么应该如何操作呢?

   我们只需在FilterAttribute类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]即可【下面类的最上面红色字体部分】,也就是让其成为可以多次执行的Action。代码如下:

1     [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
2     public class AdminFilter : ActionFilterAttribute
3     {
4 ........
5     }