在Asp.net MVC中,filter为cross-cutting concerns提供一个简单的实现方式。它共有4类Filter:

     

下边分别来讲述。

 

      1. Authorization Filter

      Authorize filter可以用于action:

    [Authorize(Users = "adam, steve, bob", Roles = "admin")]
    public ActionResult Index()

也可以直接用于controller:

    [Authorize(Roles = "Trader")]
    public class AdminController : Controller

基于AuthorizeAttribute,我们扩展一个:

    public class CustomAuthorizeAttribute : AuthorizeAttribute
    {
        private string[] allowedUsers;

        public CustomAuthorizeAttribute() : this(new string[]{})
        {

        }

        public CustomAuthorizeAttribute(params string[] users)
        {
            this.allowedUsers = users;
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            return httpContext.Request.IsAuthenticated && 
                allowedUsers != null && 
                allowedUsers.Contains(httpContext.User.Identity.Name, StringComparer.OrdinalIgnoreCase);

            //return httpContext.Request.IsLocal || base.AuthorizeCore(httpContext);
        }
    }

使用方式:

        [CustomAuthorize("Tom""bob")]
        public ActionResult Manage()
        {
            return Content("");
        }

这时候可以看到,Manage只有Tom和Bob可以访问,其他人都权限不足。如果没有登录,系统会跳转到登录界面,强制让你登录。

      显然,这种方式对于ajax请求不是很合理。好,让我们写一个ajax权限验证的filter:

    public class AjaxAuthorizeAttribute : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if(filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.Result = new JsonResult
                                           {
                                               Data = new {
                                                              Error = "Not Authorized",
                                                              LogOnUrl = new UrlHelper(filterContext.RequestContext).Action("LogOn""Account")
                                                          },
                                               JsonRequestBehavior = JsonRequestBehavior.AllowGet
                                           };
            }
            else
            {
                base.HandleUnauthorizedRequest(filterContext);
            }
        }
    }

可以看到,我们是通过重写HandleUnauthorizedRequest方法,给访问权限不足的请求的response中不仅包含错误信息,还包含一个登录url,这样js中可以自由裁决如果处理了。

 

       2. Exception Filter

       exception filter只有当action被调用,有异常抛出时被触发。当然,异常可能来源于其他filer(authorization, action, 或者 result filter)、action自身、action结果被执行时。当异常被抛出时,如果没有exception filter将ExceptionContext的ExceptinHandled设置为true,mvc将调用默认的exception filter。

       来自定义一个:

    public class CustomExceptionAttribute : FilterAttribute, IExceptionFilter
    {
        public void OnException(ExceptionContext filterContext)
        {
            if(!filterContext.ExceptionHandled && filterContext.Exception is NullReferenceException)
            {
                filterContext.Result = new RedirectResult("/SpecialErrorPage.html");
                filterContext.ExceptionHandled = true;
            }
        }
    }

可以看到,当有它捕获到异常后,跳往SpecialErrorPage.html。当异常发生时,就可以以一个人性化的页面来展示。使用如下:

        [CustomException]
        public ActionResult About()
        {
            //object nullObj = null;
            
//var str = nullObj.ToString();

            return View();
        }

     另外,内置HandleExceptionAttribute具有ExceptionType、View、Master等属性,也可以灵活利用。如:

        [HandleError(ExceptionType = typeof(NullReferenceException), Master = null, View = "NullRefer")]
        public ActionResult IamError()
        {
            object nullObj = null;

            return Content(nullObj.ToString());
        }

它可以捕获IamError内部的空引用异常,同时将以NullRefer页面来替代黄页。看页面代码:

@Model HandleErrorInfo
@{
    ViewBag.Title = "Sorry, there was a problem!";
}
<p>
There was a <b>@Model.Exception.GetType().Name</b>
while rendering <b>@Model.ControllerName</b>'s
<b>@Model.ActionName</b> action.
</p>
<p>
The exception message is: <b><@Model.Exception.Message></b>
</p>
<p>Stack trace:</p>
<pre>@Model.Exception.StackTrace</pre>

注意标黄部分,HandleErrorInfo是一个内置ViewModel,它宝航Exception、ControllerName、ActionName等属性。

 

      3. Action Filter

      先看IActionFilter接口的定义:

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

它们一个在action执行之前执行,一个在action执行之后执行。如下:

    public class ProfileAttribute : FilterAttribute, IActionFilter
    {
        private Stopwatch timer;

        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            //if(!filterContext.HttpContext.Request.IsSecureConnection)
            
//{
            
//    filterContext.Result = new HttpNotFoundResult();
            
//}

            timer = Stopwatch.StartNew();
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // do nothing

            timer.Stop();
            if (filterContext.Exception == null)
            {
                filterContext.HttpContext.Response.Write(
                string.Format("Action method elapsed time: {0}",
                timer.Elapsed.TotalSeconds));
            }
        }
    }

你可以通过自定义action filter,来对action执行时间进行监控。也可以如上文被注释的代码那样,直接在OnActionExecuting方法中给ActionResult赋值,而在OnActionExecuted中不做任何事情。

 

      4. Result Filter

      IResultFilter接口定义:

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

和action filter一样,你也可以用上述2个方法对result执行时间监控计时。mvc有个内置filter,同时实现了action filter和result filter:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
    {
        #region IActionFilter Members

        public virtual void OnActionExecuting(ActionExecutingContext filterContext);
        public virtual void OnActionExecuted(ActionExecutedContext filterContext);

        #endregion

        #region IResultFilter Members

        public virtual void OnResultExecuting(ResultExecutingContext filterContext);
        public virtual void OnResultExecuted(ResultExecutedContext filterContext);

        #endregion
    }

利用它,可以更方便的做监控了。如下:

    public class ProfileAllAttribute : ActionFilterAttribute
    {
        private Stopwatch timer;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            timer = Stopwatch.StartNew();
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            timer.Stop();
            filterContext.HttpContext.Response.Write(string.Format("Action method elapsed time: {0}", timer.Elapsed.TotalSeconds));
        }

        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            timer = Stopwatch.StartNew();
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            timer.Stop();
            filterContext.HttpContext.Response.Write(string.Format("Action result elapsed time: {0}", timer.Elapsed.TotalSeconds));
        }
    }

它可以同时输出action方法和action result消耗的时间。

 

     5 Controller 和 filter

     实际上controller类同时实现了4类filter:

public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter

所以,你可以在自己的controller里,重写上述多个方法,而不用再另外添加filter。

 

      6. 注册全局filter:

      如在RegisterGlobalFilters中加入:

            filters.Add(new ProfileAllAttribute());
            filters.Add(new HandleErrorAttribute()
            {
                ExceptionType = typeof(NullReferenceException),
                View = "SpecialError"
            });

即可。

 

     7. filter排序执行:

     自定义一个filter,来试探它们的执行顺序:

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
    public class SimpleMessageAttribute : FilterAttribute, IActionFilter
    {
        public string Message { getset; }
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Write(
            string.Format("[Before Action: {0}]", Message));
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            filterContext.HttpContext.Response.Write(
            string.Format("[After Action: {0}]", Message));
        }
    } 

测试如下:

    public class SimpleController : Controller
    {
        //[SimpleMessage(Message = "A")]
        
//[SimpleMessage(Message = "B")]
        [SimpleMessage(Message = "A", Order = 2)]
        [SimpleMessage(Message = "B", Order = 1)]
        public ActionResult Index()
        {
            return View();
        }
    }

启用备注时,注释后两行时,输出为:

before A -> Before B -> after B -> after A,但是MVC不保证每次都是这样的(A和B的执行顺序不能保证)。为了确保顺序额,如上文代码,指定Order顺序,这个时候铁定是:Before B -> Before A -> after A -> after B。 

      如果你在多个地方指定不同filter的order,它会优先global中的,然后是controller的,最后才是action的。

      mvc还有其他一些内置的filter,如OutputCache等,这里不再描述。

源码download