[ASP.NET MVC 小牛之路]11 - Filter

黄舟
發布: 2016-12-30 16:17:16
原創
1402 人瀏覽過

[ASP.NET
MVC 小牛之路]11 - Filter

Filter(篩選器)是基於AOP(面向方面​​編程)的設計,它的作用是對MVC框架處理客戶端請求注入額外的邏輯,以非常簡單優美的方式實作橫切關注點(Cross-cutting Concerns)。橫切關注點是指橫越應該程式的多個甚至所有模組的功能,經典的橫切關注點有日誌記錄、快取處理、異常處理和權限驗證等。本文將分別介紹MVC框架所支援的不同種類的Filter的創建和使用,以及如何控制它們的執行。

本文目錄


四種基本 Filter 概述

MVC框架支援的Filter可以歸為四類,每一類都可以對處理請求的不同時間點引入額外的邏輯處理。這四類Filter如下表:

[ASP.NET MVC 小牛之路]11 - Filter

在MVC框架調用acion之前,它會先判斷有沒有實現上表中的接口的特性,如果有,則在請求管道的適當的點調用特性中定義的方法。

MVC框架為這些種類的Filter介面實作了預設的特性類別。如上表,ActionFilterAttribute 類別實作了 IActionFilter 和 IResultFilter 兩個接口,這個類別是一個抽象類別,必須對它提供實作。另外兩個特性類,AuthorizeAttribute 和 HandleErrorAttribute,已經提供了一些有用的方法,可以直接使用。

Filter 既能應用在單一的ation方法上,也能應用在整個controller上,並且可以在acion和controller上應用多個Filter。如下所示:

[Authorize(Roles="trader")]  // 对所有action有效
public class ExampleController : Controller { 
 
    [ShowMessage]  // 对当前ation有效
    [OutputCache(Duration=60)] // 对当前ation有效
    public ActionResult Index() { 
        // ...  
    } 
}
登入後複製

注意,對於自訂的controller的基類,應用於該基類的Filter也將對繼承自該基類的所有子類有效。


Authorization Filter

Authorization Filter是在action方法和其他種類的Filter之前運行的。它的作用是強制實施權限策略,保證action方法只能被授權的使用者呼叫。 Authorization Filter實作的介面如下:

namespace System.Web.Mvc {
    public interface IAuthorizationFilter { 
        void OnAuthorization(AuthorizationContext filterContext); 
    } 
}
登入後複製

自訂Authorization Filter

你可以自己實作 IAuthorizationFilter 介面來建立自己的安全認證邏輯,但一般沒有這個必要也不建議這樣做。如果要自訂安全認證策略,更安全的方式是繼承預設的 AuthorizeAttribute 類別。

我們下面透過繼承 AuthorizeAttribute 類別來示範自訂Authorization Filter。新建一個空MVC應用程序,和往常的範例一樣添加一個Infrastructure 資料夾,然後添加一個CustomAuthAttribute.cs 類別文件,程式碼如下:

namespace MvcApplication1.Infrastructure {
    public class CustomAuthAttribute : AuthorizeAttribute {
        private bool localAllowed;
        public CustomAuthAttribute(bool allowedParam) {
            localAllowed = allowedParam;
        }
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
            if (httpContext.Request.IsLocal) {
                return localAllowed;
            }
            else {
                return true;
            }
        }
    }
}
登入後複製

這個簡單的Filter,透過重寫AuthorizeCore 方法,允許我們阻止本地的請求,在套用該Filter時,可以透過建構函式來指定是否允許本機請求。 AuthorizeAttribte 類別幫我們內建實現了許多東西,我們只需把重點放在 AuthorizeCore 方法上,在該方法中實現權限認證的邏輯。

為了示範這個Filter的作用,我們新建一個名為 Home 的 controller,然後在 Index action方法上應用這個Filter。參數設定為false以保護這個action 不被本地訪問,如下:

public class HomeController : Controller {

    [CustomAuth(false)]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}
登入後複製

運行程序,根據系統產生的預設路由值,將請求/Home/Index,結果如下:

[ASP.NET MVC 小牛之路]11 - Filter

我們透過把AuthorizeAttribute 類別作為基底類別自訂了一個簡單的Filter,那麼AuthorizeAttribute 類別本身作為Filter有哪些有用的功能呢?

使用內建的Authorization Filter

当我们直接使用 AuthorizeAttribute 类作为Filter时,可以通过两个属性来指定我们的权限策略。这两个属性及说明如下:

Users属性,string类型,指定允许访问action方法的用户名,多个用户名用逗号隔开。
Roles属性,string类型,用逗号分隔的角色名,访问action方法的用户必须属于这些角色之一。

使用如下:

public class HomeController : Controller {

    [Authorize(Users = "jim, steve, jack", Roles = "admin")]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}
登入後複製

这里我们为Index方法应用了Authorize特性,并同时指定了能访问该方法的用户和角色。要访问Index action,必须两者都满足条件,即用户名必须是 jim, steve, jack 中的一个,而且必须属性 admin 角色。

另外,如果不指定任何用户名和角色名(即 [Authorize] ),那么只要是登录用户都能访问该action方法。

你可以通过创建一个Internet模板的应用程序来看一下效果,这里就不演示了。

对于大部分应用程序,AuthorizeAttribute 特性类提供的权限策略是足够用的。如果你有特殊的需求,则可以通过继承AuthorizeAttribute 类来满足。


Exception Filter

Exception Filter,在下面三种来源抛出未处理的异常时运行:

另外一种Filter(如Authorization、Action或Result等Filter)。
Action方法本身。
Action方法执行完成(即处理ActionResult的时候)。

Exception Filter必须实现 IExceptionFilter 接口,该接口的定义如下:

namespace System.Web.Mvc { 
    public interface IExceptionFilter { 
        void OnException(ExceptionContext filterContext); 
    } 
    }
登入後複製

ExceptionContext 常用属性说明

在 IExceptionFilter 的接口定义中,唯一的 OnException 方法在未处理的异常引发时执行,其中参数的类型:ExceptionContext,它继承自 ControllerContext 类,ControllerContext 包含如下常用的属性:

Controller,返回当前请求的controller对象。
HttpContext,提供请求和响应的详细信息。
IsChildAction,如果是子action则返回true(稍后将简单介绍子action)。
RequestContext,提供请求上下文信息。
RouteData,当前请求的路由实例信息。

作为继承 ControllerContext 类的子类,ExceptionContext 类还提供了以下对处理异常的常用属性:

ActionDescriptor,提供action方法的详细信息。
Result,是一个 ActionResult 类型,通过把这个属性值设为非空可以让某个Filter的执行取消。
Exception,未处理异常信息。
ExceptionHandled,如果另外一个Filter把这个异常标记为已处理则返回true。

一个Exception Filter可以通过把 ExceptionHandled 属性设置为true来标注该异常已被处理过,这个属性一般在某个action方法上应用了多个Exception Filter时会用到。ExceptionHandled 属性设置为true后,就可以通过该属性的值来判断其它应用在同一个action方法Exception Filter是否已经处理了这个异常,以免同一个异常在不同的Filter中重复被处理。

示例演示

在 Infrastructure 文件夹下添加一个 RangeExceptionAttribute.cs 类文件,代码如下:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
    public void OnException(ExceptionContext filterContext) {
        if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {
            filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html");
            filterContext.ExceptionHandled = true;
        }
    }
}
登入後複製

这个Exception Filter通过重定向到Content目录下的一个静态html文件来显示友好的 ArgumentOutOfRangeException 异常信息。我们定义的 RangeExceptionAttribute 类继承了FilterAttribute类,并且实现了IException接口。作为一个MVC Filter,它的类必须实现IMvcFilter接口,你可以直接实现这个接口,但更简单的方法是继承 FilterAttribute 基类,该基类实现了一些必要的接口并提供了一些有用的基本特性,比如按照默认的顺序来处理Filter。

在Content文件夹下面添加一个名为RangeErrorPage.html的文件用来显示友好的错误信息。如下所示:

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>One of the arguments was out of the expected range.</span> 
</body> 
</html>
登入後複製

在HomeController中添加一个值越限时抛出异常的action,如下所示:

public class HomeController : Controller { 
        [RangeException]
        public string RangeTest(int id) { 
            if (id > 100) { 
                return String.Format("The id value is: {0}", id); 
            } else { 
                throw new ArgumentOutOfRangeException("id", id, ""); 
            } 
        } 
    }
登入後複製

当对RangeTest应用自定义的的Exception Filter时,运行程序URL请求为 /Home/RangeTest/50,程序抛出异常后将重定向到RangeErrorPage.html页面:

[ASP.NET MVC 小牛之路]11 - Filter

由于静态的html文件是和后台脱离的,所以实际项目中更多的是用一个View来呈现友好的错误信息,以便很好的对它进行一些动态的控制。如下面把示例改动一下,RangeExceptionAttribute 类修改如下:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
        public void OnException(ExceptionContext filterContext) {
            if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {                int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue);
                filterContext.Result = new ViewResult {
                    ViewName = "RangeError",
                    ViewData = new ViewDataDictionary<int>(val)
                };
                filterContext.ExceptionHandled = true;
            }
        }
    }
登入後複製

我们创建一个ViewResult对象,指定了发生异常时要重定向的View名称和传递的model对象。然后我们在Views/Shared文件夹下添加一个RangeError.cshtml文件,代码如下:

@model int

<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @Model was out of the expected range.</span> 
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
</body> 
</html>
登入後複製

运行结果如下:

[ASP.NET MVC 小牛之路]11 - Filter

禁用异常跟踪

很多时候异常是不可预料的,在每个Action方法或Controller上应用Exception Filter是不现实的。而且如果异常出现在View中也无法应用Filter。如RangeError.cshtml这个View加入下面代码:

@model int

@{ 
    var count = 0; 
    var number = Model / count; 
} 

...
登入後複製

运行程序后,将会显示如下信息:

[ASP.NET MVC 小牛之路]11 - Filter

显然程序发布后不应该显示这些信息给用户看。我们可以通过配置Web.config让应用程序不管在何时何地引发了异常都可以显示统一的友好错误信息。在Web.config文件中的节点下添加如下子节点:

<system.web>
    
    ...
    <customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
  </system.web>
登入後複製

这个配置只对远程访问有效,本地运行站点依然会显示跟踪信息。

使用内置的 Exceptin Filter

通过上面的演示,我们理解了Exceptin Filter在MVC背后是如何运行的。但我们并不会经常去创建自己的Exceptin Filter,因为微软在MVC框架中内置的 HandleErrorAttribute(实现了IExceptionFilter接口) 已经足够我们平时使用。它包含ExceptionType、View和Master三个属性。当ExceptionType属性指定类型的异常被引发时,这个Filter将用View属性指定的View(使用默认的Layout或Mast属性指定的Layout)来呈现一个页面。如下面代码所示:

... 
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")] 
public string RangeTest(int id) { 
    if (id > 100) { 
        return String.Format("The id value is: {0}", id); 
    } else { 
        throw new ArgumentOutOfRangeException("id", id, ""); 
    } 
} 
...
登入後複製

使用内置的HandleErrorAttribute,将异常信息呈现到View时,这个特性同时会传递一个HandleErrorInfo对象作为View的model。HandleErrorInfo类包含ActionName、ControllerName和Exception属性,如下面的 RangeError.cshtml 使用这个model来呈现信息:

@model HandleErrorInfo 
@{ 
    ViewBag.Title = "Sorry, there was a problem!"; 
} 
 
<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue) 
        was out of the expected range.</span>         
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
    <div style="display: none"> 
        @Model.Exception.StackTrace 
    </div> 
</body> 
</html>
登入後複製

Action Filter

顾名思义,Action Filter是对action方法的执行进行“筛选”的,包括执行前和执行后。它需要实现 IActionFilter 接口,该接口定义如下:

namespace System.Web.Mvc { 
    public interface IActionFilter { 
        void OnActionExecuting(ActionExecutingContext filterContext); 
        void OnActionExecuted(ActionExecutedContext filterContext); 
    } 
}
登入後複製

其中,OnActionExecuting方法在action方法执行之前被调用,OnActionExecuted方法在action方法执行之后被调用。我们来看一个简单的例子。

在Infrastructure文件夹下添加一个ProfileActionAttribute类,代码如下:

using System.Diagnostics; 
using System.Web.Mvc; 

namespace MvcApplication1.Infrastructure { 
    public class ProfileActionAttribute : FilterAttribute, IActionFilter { 
        private Stopwatch timer; 
        public void OnActionExecuting(ActionExecutingContext filterContext) { 
            timer = Stopwatch.StartNew(); 
        } 
        public void OnActionExecuted(ActionExecutedContext filterContext) { 
            timer.Stop();
            if (filterContext.Exception == null) { 
                filterContext.HttpContext.Response.Write( 
                    string.Format("<div>Action method elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); 
            } 
        } 
    } 
}
登入後複製

在HomeController中添加一个Action并应用该Filter,如下:

... 
[ProfileAction] 
public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...
登入後複製

运行程序,URL定位到/Home/FilterTest,结果如下:

[ASP.NET MVC 小牛之路]11 - Filter

我们看到,ProfileAction的 OnActionExecuted 方法是在 FilterTest 方法返回结果之前执行的。确切的说,OnActionExecuted 方法是在action方法执行结束之后和处理action返回结果之前执行的。

OnActionExecuting方法和OnActionExecuted方法分别接受ActionExecutingContext和ActionExecutedContext对象参数,这两个参数包含了ActionDescriptor、Canceled、Exception等常用属性。


Result Filter

Result Filter用来处理action方法返回的结果。用法和Action Filter类似,它需要实现 IResultFilter 接口,定义如下:

namespace System.Web.Mvc { 
    public interface IResultFilter { 
        void OnResultExecuting(ResultExecutingContext filterContext); 
        void OnResultExecuted(ResultExecutedContext filterContext); 
    } 
}
登入後複製

IResultFilter 接口和之前的 IActionFilter 接口类似,要注意的是Result Filter是在Action Filter之后执行的。两者用法是一样的,不再多讲,直接给出示例代码。

在Infrastructure文件夹下再添加一个 ProfileResultAttribute.cs 类文件,代码如下:

public class ProfileResultAttribute : FilterAttribute, IResultFilter { 
    private Stopwatch timer; 
    public void OnResultExecuting(ResultExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Result elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}
登入後複製

应用该Filter:

... 
[ProfileAction] 
[ProfileResult] public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...
登入後複製

内置的 Action 和 Result Filter

MVC框架内置了一个 ActionFilterAttribute 类用来创建action 和 result 筛选器,即可以控制action方法的执行也可以控制处理action方法返回结果。它是一个抽象类,定义如下:

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) { 
        } 
    } 
}
登入後複製

使用这个抽象类方便之处是你只需要实现需要加以处理的方法。其他和使用 IActionFilter 和 IResultFilter 接口没什么不同。下面是简单做个示例。

在Infrastructure文件夹下添加一个 ProfileAllAttribute.cs 类文件,代码如下:

public class ProfileAllAttribute : ActionFilterAttribute { 
    private Stopwatch timer; 
    public override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write(
        string.Format("<div>Total elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}
登入後複製

在HomeController中的FilterTest方法上应用该Filter:

... 
[ProfileAction] 
[ProfileResult] 
[ProfileAll] 
public string FilterTest() { 
    return "This is the FilterTest action"; 
} 
...
登入後複製

运行程序,URL定位到/Home/FilterTest,可以看到一个Action从执行之前到结果处理完毕总共花的时间:

[ASP.NET MVC 小牛之路]11 - Filter

我们也可以Controller中直接重写 ActionFilterAttribute 抽象类中定义的四个方法,效果和使用Filter是一样的,例如:

public class HomeController : Controller { 
    private Stopwatch timer; 
    ...
    public string FilterTest() { 
        return "This is the FilterTest action"; 
    } 
    protected override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    protected override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Total elapsed time: {0}</div>", 
            timer.Elapsed.TotalSeconds)); 
    }
登入後複製

注册为全局 Filter

全局Filter对整个应用程序的所有controller下的所有action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,可以把一个Filter类注册为全局,如:

using System.Web; 
using System.Web.Mvc; 
using MvcApplication1.Infrastructure; 
 
namespace MvcApplication1 { 
    public class FilterConfig { 
        public static void RegisterGlobalFilters(GlobalFilterCollection filters) { 
            filters.Add(new HandleErrorAttribute()); 
            filters.Add(new ProfileAllAttribute()); 
        } 
    } 
}
登入後複製

我们增加了filters.Add(new ProfileAllAttribute())这行代码,其中的filters参数是一个GlobalFilterCollection类型的集合。为了验证 ProfileAllAttribute 应用到了所有action,我们另外新建一个controller并添加一个简单的action,如下:

public class CustomerController : Controller { 
        public string Index() { 
            return "This is the Customer controller"; 
        } 
}
登入後複製

运行程序,将URL定位到 /Customer ,结果如下:

[ASP.NET MVC 小牛之路]11 - Filter


其它常用 Filter

MVC框架内置了很多Filter,常见的有RequireHttps、OutputCache、AsyncTimeout等等。下面例举几个常用的。

RequireHttps,强制使用HTTPS协议访问。它将浏览器的请求重定向到相同的controller和action,并加上 https:// 前缀。
OutputCache,将action方法的输出内容进行缓存。
AsyncTimeout/NoAsyncTimeout,用于异步Controller的超时设置。(异步Controller的内容请访问 xxxxxxxxxxxxxxxxxxxxxxxxxxx)
ChildActionOnlyAttribute,使用action方法仅能被Html.Action和Html.RenderAction方法访问。

这里我们选择 OutputCache 这个Filter来做个示例。新建一个 SelectiveCache controller,代码如下:

public class SelectiveCacheController : Controller {
    public ActionResult Index() { 
        Response.Write("Action method is running: " + DateTime.Now); 
        return View(); 
    } 

    [OutputCache(Duration = 30)] 
    public ActionResult ChildAction() { 
        Response.Write("Child action method is running: " + DateTime.Now); 
        return View(); 
    } 
}
登入後複製


这里的 ChildAction 应用了 OutputCache filter,这个action将在view内被调用,它的父action是Index。

现在我们分别创建两个View,一个是ChildAction.cshtml,代码如下:

@{ 
    Layout = null; 
} 
 
<h4>This is the child action view</h4>
登入後複製

另一个是它的Index.cshtml,代码如下:

@{ 
    ViewBag.Title = "Index"; 
} 
 
<h2>This is the main action view</h2> 
 
@Html.Action("ChildAction")
登入後複製

运行程序,将URL定位到 /SelectiveCache ,过几秒刷新一下,可看到如下结果:

[ASP.NET MVC 小牛之路]11 - Filter

[ASP.NET MVC 小牛之路]11 - Filter

 以上就是[ASP.NET MVC 小牛之路]11 - Filter的内容,更多相关内容请关注PHP中文网(www.php.cn)!


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板