Filter in ASP.NET MVC

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

     Filter in ASP.NET MVC_第1张图片

下边分别来讲述。

 

      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 

你可能感兴趣的:(asp.net)