Fork me on GitHub

Action Filtering in ASP.NET MVC Applications

Base Interview

MVC中一个重要的功能——Filter,我们通过研究源代码来了解Filter的原理,以及AOP 模式和各种Filter的执行。最重要的是大家通过理解Filter的代码,明白Filter的机制,从而对Filter有一个灵活的运用。

比如你在Action上使用的每一个 [Attribute]大都是Filter,mvc提供四种类型的 Filter:IActionFilter,IAuthorizationFilter,IExceptionFilter,IResultFilter,这四种Filter足以我们所要实现的功能了,还提供了几个现成的可以使用的Filter:OutputCacheAttribute、 HandleErrorAttribute、AuthorizeAttribute。我们知道Filter是横切在Action上的,所以每次执行一个 Action,就会牵扯到这个Action的执行,不同类型的Filter提供不同类型的操作,Filter的设计就是一种AOP设计模式,所以这种方式十分灵活。

AuthorizeAttribute 介绍如何使用 Authorize 特性控制对操作方法的访问。
OutputCacheAttribute 介绍如何使用 OutputCache 特性缓存操作方法的输出结果。
HandleErrorAttribute 解释如何向 MVC 应用程序添加自定义操作筛选器。

对于四种类型的Filter,各自有不同的方法,并以此来实现不同的功能,我们可以发挥自己的创造力,定制一个自己的Filter,来完成自己想要的功能。一般的Filter应用的场景有:缓存、验证、异常,以及处理Action上下文等,要想实现自己的Filter 只要继承相应的接口并重写接口的方法就可以了。

  • 授权筛选器,用于做出关于是否执行操作方法(如执行身份验证或验证请求的属性)的安全决策。 AuthorizeAttribute 类是授权筛选器的一个示例。
  • 操作筛选器,用于包装操作方法的执行操作。此筛选器可以执行其他处理,如向操作方法提供额外数据、检查返回值或取消执行操作方法。
  • 结果筛选器,用于包装 ActionResult 对象的执行操作。此筛选器可以对结果执行其他处理,如修改 HTTP 响应。 OutputCacheAttribute 类是结果筛选器的一个示例。
  • 异常筛选器,如果在操作方法中某处引发了未经处理的异常,则从授权筛选器开始执行,执行结果筛选器后结束。异常筛选器可用于执行诸如日志记录或显示错误页之类的任务。 HandleErrorAttribute 类是异常筛选器的一个示例

Why Use Filter


我们知道Filter是一种AOP模式,也就是说它提供我们可以对一系列操作进行横切干扰的手段,而且它解耦了依赖关系。想想这么一种场景,在传统的WebForm中,某一个页面的访问必须去验证当前用户是否符合某一个要求,比如已登录,这时,我们需要获取请求的上下文,分析当前用户的行为状态,从而得到这个条件是否成立,如果我们有很多这种页面,比如所有的后台管理页面需要管理员的登录,我们是不是每一页都写一个这样的验证呢?我们当然不会这么蠢,我们会聪明的把验证的逻辑封装成一个验证类,然后从各个页面调用验证类的验证逻辑,从而得知当前用户是否通过验证。
这样还是不够好,因为我们不得不在每一处反复写我们的调用验证类的验证代码,显然这些工作是重复的,因为那个时候还没有AOP的概念,所以我们不得不多付出点代价。
现在有了mvc,它理所应当为我们提供一个AOP的框架,而Filter就是这么一种模式,它的出现容许我们以一种更为简单的方式来实现类似的功能,在使用的时候我们只需要像该某个商品贴标签式的方式给Action“贴”上一个Attribute就行了,一切搞定,没有一点多于的代码。

Use default Filter

操作筛选器的某些可能用途包括:

  • 日志记录,目的是跟踪用户交互。
  • “反图像攫取”,用于防止在自己网站之外的网页中加载图像。
  • 爬网程序筛选,用于根据浏览器用户代理来更改应用程序行为。
  • 本地化,用于设定区域设置。
  • 动态操作,用于将操作注入到控制器中。

操作筛选器是以从 ActionFilterAttribute 中继承的特性类的形式实现的。 ActionFilterAttribute 是一个具有以下四个可以重写的虚拟方法的抽象类:OnActionExecuting、OnActionExecuted、OnResultExecuting 和 OnResultExecuted。若要实现操作筛选器,您至少必须重写这些方法之一。

ASP.NET MVC 框架将先调用操作筛选器的 OnActionExecuting 方法,然后再调用以操作筛选器特性标记的任意操作方法。同样,该框架将在操作方法完成后调用 OnActionExecuted 方法。

调用 OnResultExecuting 方法后,要立即调用您的操作返回的 ActionResult 实例。执行结果后,紧接着就要调用 OnResultExecuted 方法。这些方法对于执行日志记录、缓存输出结果之类的操作非常有用。

下面演示自带的Filter 如OutputCacheAttribute

配置缓存文件如下

<system.web>
  <caching>
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="MyProfile" duration="60" varyByParam="*" />
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
</system.web>

标记制作器中的Action

[OutputCache(CacheProfile = "MyProfile", Duration = 10)]
public ActionResult About()
{
    ViewData["Message"] = "This page was cached at " + DateTime.Now;

    return View();
}

Creating a custom action filter

下面的示例演示一个简单的操作筛选器,该操作筛选器记录调用操作方法之前和之后的跟踪消息

注意: 按照约定,操作筛选器特性的名称应以“Attribute”作为结尾,LogFilterAttribute 或 OutputFilterAttribute。如创建一个日志异常的Filter

public class LoggingFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Trace.Write("(Logging Filter)Action Executing: " +
            filterContext.ActionDescriptor.ActionName);

        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Exception != null)
            filterContext.HttpContext.Trace.Write("(Logging Filter)Exception thrown");

        base.OnActionExecuted(filterContext);
    }
}

使用也很简单 ,用操作筛选器特性标记的操作方法的控制器

        [LoggingFilter]
        public ActionResult Index()
        {
            ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";

            return View();
        }
        [LoggingFilter]
        public ActionResult About()
        {
            ViewBag.Message = "你的应用程序说明页。";

            return View();
        }

或者在控制器中重写

image

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Trace.Write("(Controller)Action Executing: " +
                filterContext.ActionDescriptor.ActionName);

            base.OnActionExecuting(filterContext);
        }
        [NonAction]
        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.Exception != null)
                filterContext.HttpContext.Trace.Write("(Controller)Exception thrown");

            base.OnActionExecuted(filterContext);
        }
        [LoggingFilter]
        public ActionResult Index()
        {
            ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";

            return View();
        }
        [LoggingFilter]
        public ActionResult About()
        {
            ViewBag.Message = "你的应用程序说明页。";

            return View();
        }

或者注册全局的Filter,在App_Start 中的 FilterConfig.cs文件中(MVC4)注册我们的LoggingFilterAttribute

      public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new LoggingFilterAttribute());
            filters.Add(new HandleErrorAttribute());
        }

我们也可以看下HandleErrorAttribute类 F12

// 摘要:
    //     表示一个特性,该特性用于处理由操作方法引发的异常。
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class HandleErrorAttribute : FilterAttribute, IExceptionFilter
    {
        // 摘要:
        //     初始化 System.Web.Mvc.HandleErrorAttribute 类的新实例。
        public HandleErrorAttribute();

        // 摘要:
        //     获取或设置异常的类型。
        //
        // 返回结果:
        //     异常的类型。
        public Type ExceptionType { get; set; }
        //
        // 摘要:
        //     获取或设置用于显示异常信息的母版视图。
        //
        // 返回结果:
        //     母版视图。
        public string Master { get; set; }
        //
        // 摘要:
        //     获取此特性的唯一标识符。
        //
        // 返回结果:
        //     此特性的唯一标识符。
        public override object TypeId { get; }
        //
        // 摘要:
        //     获取或设置用于显示异常信息的页视图。
        //
        // 返回结果:
        //     页视图。
        public string View { get; set; }

        // 摘要:
        //     在发生异常时调用。
        //
        // 参数:
        //   filterContext:
        //     操作-筛选器上下文。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     filterContext 参数为 null。
        public virtual void OnException(ExceptionContext filterContext);
    }

如我看可以看到 Master的额外参数,我们可以在控制器中如下标记

       [HandleErrorAttribute(Master="我是参数 但不是模板页")]
        public ActionResult Contact()
        {
            ViewBag.Message = "你的联系方式页。";

            return View();
        }

我们也可以添加我们想要的值,如添加一个属性在 (public string Message { get; set; } )在LoggingFilterAttribute 类

我们也可以把该filter特性标记上面的方法中,如:

[LoggingFilterAttribute (Message = "action")]

public class IndexController : Controller

{…}

全局中我们可以通过下面方式获取控制器与action的名称

filterContext.RouteData.GetRequiredString("Controller") + filterContext.RouteData.GetRequiredString("action");

Summary

在本篇文章中,讲述的内容非常有限,主要讨论了怎么获取Filter的,以及Controller与各种 Filter的关系,还有Filter的4种类型,但是我们没有具体讨论4种类型Filter的方法。还有最终要的就是大家通过这个探究这个过程,真正了理解Filter的设计思想和AOP在mvc中的运用,以及Filter的功能强大之处。

Link:

http://msdn.microsoft.com/zh-cn/library/dd410209(v=vs.100).aspx

http://weblogs.asp.net/rashid/archive/2010/05/27/mvcextensions-actionfilter.aspx

posted @ 2012-12-11 14:27  花儿笑弯了腰  阅读(399)  评论(0编辑  收藏  举报