Asp.Net MVC 系列--進階篇之Filter
使用Filter
Filter是很好的實現crosscutting concern 的方式,常見的crosscutting concern包括log,驗證,快取,異常處理(筆者推薦postsharp),等等。
什麼是crosscutting concern?
為了更好的專注業務的實現,降低耦合,提高內聚。因此把這些concern從業務中抽離出來解決,可以更好的維護,更改和擴充套件。它們也構成了架構中cross-cutting的部分,可以在與業務隔離的情況下,統一編碼,測試,修改。
言歸正傳--Filter
例如AdminController :
public class AdminController : Controller {
// ...instance variables and constructor
public ViewResult Index() {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
}
public ViewResult Create() {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
}
public ViewResult Edit(int productId) {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
}
}
我們的目的很明確,我需要限制這個controller的訪問,只有登入的才能訪問,那麼我們每個action都要加上Request.IsAuthenticated.如果需要限制role,指定使用者才能訪問,我們就要寫一個驗證函式,並修改全部的action。
應用了Filter的實現
[Authorize]
public class AdminController : Controller {
public ViewResult Index() {
}
public ViewResult Create() {
}
public ViewResult Edit(int productId) {
}
}
好處:可以看到filter提高了程式碼的可讀性,減少了重複。
FilterAttribute的邏輯注入在http pipeline裡面,在controller之前,反射出所有controller,查詢customize的attribute,如果有加Authorize,呼叫相應的login驗證函式(後續章節會介紹),驗證不通過則返回401 。
關於Attribute
Attribute是.net 中很好應用裝飾模式的例子,通過繼承自System.Attribute,可以實現自己的attribute,包括class,method,property,field。目的是動態的注入property,member,和ability到相應的類中,編譯之後,生成的IL已經注入了attribute要提供的能力。通常的AOP框架中有attribute和IL wave 一起使用的程式碼和使用方法。不熟悉的讀者可以查閱相關MSDN,本章所有內容都建立在attribute有一個基本的認識上的。
MVC中四種基本的filter
Authorize Filter |
驗證。進入controller或action之前 |
Action Filter |
進入action前後會被呼叫 |
Result Filter |
Execute result的前後會被呼叫 |
Exception Filter |
Filter,action,或者execute result時發生異常時被觸發 |
給controller和action加 Filter:
MVC中的authorizefilter除了可以加在class上(前面演示過了),還可以針對action做filter:
public class AdminController : Controller {
[Authorize]
public ViewResult Index() {
}
[Authorize]
public ViewResult Create() {
}
}
也可以apply多個filter:
[Authorize(Roles="trader")]
public class ExampleController : Controller {
[ShowMessage]// applies to just this action
[OutputCache(Duration=60)]// applies to just this action
public ActionResult Index() {
// ...action method body
}
}
Customize filter
public class BlockAttribute : AuthorizeAttribute {
public BlockAuthAttribute() {
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return false;
}
}
作為演示,實現了一個block attribute,加上這個attribute會block所有的request。現實場景中,拿到了httpContext可以做很多事情,因為裡面的物件包含的資訊非常豐富。
使用:
[Block]
public string Index() {
return "Always block ";
}
訪問頁面會發現:
由於我建立的是basic的專案,沒有加login view在Account,因此找不到login。但是filter已經生效了。
當使用AuthorizeAttribute,還有Users和Roles filter也很常用,使用方法:
[Authorize(Users= "iori", Roles = "admin")]
public string Index() {
return "This is the Index action on the Home controller";
}
這條filter就限制了訪問這個action,需要user為iori,並且role為admin,關於MVC Authorize使用細節,如何完成驗證,完成AccountController,會有專門一章介紹,在此只演示MVC有這樣一個filter可以使用。
Exception Filter
Customize Exception Filter
需要實現介面:
public interface IExceptionFilter {
void OnException(ExceptionContextfilterContext);
}
FilterContext物件包含:
Controller |
發生異常時的Controller Object |
HttpContext |
當前HttpContext物件 |
IsChildAction |
是否為子action |
RequestContext |
HttpContext物件 |
RouteData |
返回當前request的route資料 |
ActionDescriptor |
Action 方法的描述 |
Result |
Action method的result,可以修改result |
Exception |
異常物件 |
ExceptionHandled |
如果其他filter已經handle,則為true |
發生exception時候,拿到以上資訊,除了記log,還可以做的事情:
頁面跳轉,跳轉到錯誤頁面
直接給客戶端返回response
重寫result返回給action
實現:
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;
}
}
}
程式碼做的事情很簡單,判斷異常是否被handle,如果沒有並且是argumentOutOfRange型別的exception,跳轉到一個友好的錯誤頁面。
錯誤頁面程式碼:
<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>
使用這個Exception filter:
[RangeException]
public ActionResult Index()
{
throw new ArgumentOutOfRangeException();
}
驗證:
使用自帶的Exception Filter – HandleErrorAttribute
ExceptionType |
Exception type will be handled by this filter |
View |
View Name |
Master |
Layout Name |
Web config 配置 default redirect
<customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
Apply filter
[HandleError(ExceptionType= typeof(ArgumentOutOfRangeException), View = "RangeError")]
public ActionResult Index()
{
throw new ArgumentOutOfRangeException();
}
RangeErrorView中會收到HandleErrorInfo 物件:
Action Name |
發生異常的action name |
Controller Name |
發生異常的controller name |
Exception |
Exception物件 |
View 程式碼:
@model HandleErrorInfo
@{
ViewBag.Title= "Sorry, there was a problem!";
}
<!DOCTYPEhtml>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RangeError</title>
</head>
<body>
<h2>Sorry</h2>
<span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue)
was out ofthe 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
介面:
public interface IActionFilter { void OnActionExecuting(ActionExecutingContext filterContext); void OnActionExecuted(ActionExecutedContext filterContext); }
OnActionExecuting
在action執行前被呼叫
OnActionExecuted
在action執行完畢被呼叫(返回值之前)
CustomizeAction Attribute 實現
public class ActionTestAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write("<br/>Written fromAction Filter.Should Before Execute action body");
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write("<br/>Written fromAction filter,Should After Execute action body.");
}
}
目的很顯然,在ActionExecuting和ActionExecuted分別列印一行字。
應用這個Filter
[ActionTest]
public ActionResult Index()
{
Response.Write("<div>ActionBody</div>");
return Content("<br/>Action Result.");
}
驗證結果:
可以看到執行順序:
·ActionFilter Executing
·Action body
·ActionFilter Executed
·Action body return result
引數常用物件
ActionExecutingContext物件:
Action Descriptor |
Action的描述 |
Result |
返回值 |
HttpContext |
當前的HttpContext物件 |
ActionExecutedContext稍有不同,我們能拿到更多的object
ActionDescriptor |
Action描述 |
Result |
Action Result |
Canceled |
如果action已經被(其他filter)cancel會被標記為true |
Exception |
異常物件 |
ExceptionHandled |
異常是否被handle了 |
HttpContext |
當前HttpContext物件 |
Result Filter
介面
public interface IResultFilter {
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
程式碼實現:
public class ResultTestAttribute :FilterAttribute, IResultFilter
{
private Stopwatch timer;
public void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write("<br/>----------StartExecuting Result.--------<br/>");
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));
}
}
開始執行Result時,開啟一個stop watch,在executed關閉,為了計時執行result用了多少時間,同時在executing和executed分別列印一行字。
應用這個filter:
[ResultTest]
[ActionTest]
public ActionResult Index()
{
Response.Write("<div>ActionBody</div>");
return Content("<br/>Return Action Result.");
}
和剛才演示的actionfilter一起應用,正好看一下執行的順序。
驗證結果:
可見執行順序:
2.ActionExecuting
3.Actionbody
4.ActionExecuted
5.ResultExecuting
6.Actionbody return result
7.Resultexecuted
使用自帶的ActionFilterAttribute
ActionFilter實際提供了四個virtual的函式,都是剛剛提到的:
public virtual void OnActionExecuting(ActionExecutingContext filterContext) {
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext) {
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext) {
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext) {
}
繼承這個類,根據實際場景的需要,我們可以選擇性的override其中的幾個
程式碼實現
public class ActionResultTestAttribute :ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContextfilterContext)
{
filterContext.HttpContext.Response.Write("<br />[ActionFilterUsage] Action Executing...");
}
public override void OnResultExecuted(ResultExecutedContextfilterContext)
{
filterContext.HttpContext.Response.Write("<br />[ActionFilterUsage] Result Executed");
}
}
以上程式碼演示了繼承ActionFilterAttribute,重寫OnActionExecuting和OnResultExecuted這兩個函式,為了驗證看的清楚,列印時加了特殊的標記。
使用:
[ActionResultTest]
[ResultTest]
[ActionTest]
public ActionResult Index()
{
Response.Write("<div>Action Body</div>");
return Content("<br/>Return Action Result.");
}
驗證結果:
可以看到執行順序:
·Action filter 中的Action Executing
·自定義的Action Executing
·Action Body
·自定義的Action Executed
·自定義的Result Executing
·Action Result
·自定義的Result Executed
·Action Filter中的Result Executed
給controller加Action,ResultExecuting, Executed
由於controller基類已經提供了virtual,因此直接重寫就好了:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
Response.Write("<div>Action Body</div>");
return Content("<br/>Return Action Result.");
}
protected override void OnActionExecuting(ActionExecutingContextfilterContext)
{
Response.Write("[From Controller ]: On action Executing.");
}
protected override void OnResultExecuted(ResultExecutedContextfilterContext)
{
Response.Write("[From Controller ] : On Result Executed.");
}
}
驗證結果:
可以看到controller級別的filter生效了。雖然controller提供了virtual使得我們可以重寫來完成controller級別的filter,但是仍然建議自定義或者使用預設的actionfilter。
使用全域性filter
1.開啟App_Start 目錄的filter config
讓我們把剛才自定義的ActionTestFilter變成全域性的:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(newHandleErrorAttribute());
filters.Add(newActionTestAttribute());
}
}
這樣一來,任何一個Action的執行,都會呼叫剛才定義的ActionTestFilter。
Action程式碼(記得拿掉ActionFilter以及剛才controller重寫的):
public ActionResult Index()
{
Response.Write("<div>Action Body</div>");
return Content("<br/>Return Action Result.");
}
驗證:
可見,自定義filter已經成功被加在全域性。
Filter 排序
這個功能只針對Multiple Filter (也就是定義Attribute的時候,要AllowMultiple)
例子:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)]
public class SomeMessageAttribute :FilterAttribute, IActionFilter
{
public string Message { get; set; }
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write(
string.Format("<div>[Before Action] [Message: <{0}>]<div>", Message));
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write(
string.Format("<div>[After Action] [Message: <{0}>]<div>", Message));
}
}
程式碼說明:在executing和executed分別列印出message。
應用這個filter(為了分開演示清楚,記得把剛才全域性filter拿掉):
[SomeMessage(Message = "A",Order =1)]
[SomeMessage(Message = "B",Order = 2)]
[SomeMessage(Message = "C",Order = 3)]
public ActionResult Index()
{
Response.Write("<div>Action Body</div>");
return Content("<br/>Return Action Result.");
}
我們加了三個filter,並安排了執行順序。
檢視結果:
可見按著我們期望的結果,現在調整一下順序:
[SomeMessage(Message = "A",Order = 3)]
[SomeMessage(Message = "B",Order = 1)]
[SomeMessage(Message = "C",Order = 2)]
驗證結果:
期望結果是B – C – A
檢視結果:
其他一些常用filter
RequireHttps:限制只接受https協議
OutputCache : 和Web Form中的使用方法類似,傳遞一個duration,就可以享受頁面快取了
Post:限制請求型別必須為post
Get:限制請求型別必須為Get
相關文章
- Asp.Net MVC4系列--進階篇之AJAXASP.NETMVC
- Asp.Net MVC4 系列--進階篇之ViewASP.NETMVCView
- Asp.Net MVC4 系列--進階篇之Model(1)ASP.NETMVC
- Asp.Net MVC4 系列--進階篇之Model(2)ASP.NETMVC
- Asp.Net MVC系列--進階篇之controller(1)ASP.NETMVCController
- Asp.Net MVC4系列--進階篇之Helper(1)ASP.NETMVC
- Asp.Net MVC4 系列--進階篇之Helper(2)ASP.NETMVC
- Asp.Net MVC4 系列--進階篇之Controller(2)ASP.NETMVCController
- Asp.Net MVC4 系列-- 進階篇之路由(1)ASP.NETMVC路由
- Asp.Net MVC4 系列--進階篇之路由 (2)ASP.NETMVC路由
- ASP.NET MVC FilterASP.NETMVCFilter
- Asp.Net MVC 系列--基礎篇(2)ASP.NETMVC
- Asp.Net MVC系列--基礎篇(3)ASP.NETMVC
- 【webpack 系列】進階篇Web
- [ASP.NET MVC 小牛之路]11 - FilterASP.NETMVCFilter
- Asp.Net MVC4 系列--基礎篇(1)ASP.NETMVC
- Asp.Net MVC4系列---基礎篇(5)ASP.NETMVC
- Asp.Net MVC4系列---基礎篇(4)ASP.NETMVC
- ASP.NET MVC系列:AreaASP.NETMVC
- ASP.NET MVC系列:ModelASP.NETMVC
- ASP.NET MVC學習之模型驗證篇ASP.NETMVC模型
- ASP.NET MVC+EF框架+EasyUI實現許可權管理系列之開篇ASP.NETMVC框架UI
- ASP.NET MVC使用Filter解除Session, Cookie等依賴ASP.NETMVCFilterSessionCookie
- ASP.NET MVC 之 AJAXASP.NETMVC
- .NET進階系列之四:深入DataTable
- python常用函式進階(2)之map,filter,reduce,zipPython函式Filter
- 帶你深度解鎖Webpack系列(進階篇)Web
- 正規表示式系列之中級進階篇
- Asp.net core mvc裡面怎麼新增全域性的FilterASP.NETMVCFilter
- 使用Filter跟蹤ASP.NET MVC頁面載入時間FilterASP.NETMVC
- Java多執行緒之進階篇Java執行緒
- Membership三步曲之進階篇
- 【第三篇】ASP.NET MVC快速入門之安全策略(MVC5+EF6)ASP.NETMVC
- Java進階篇 設計模式之十四 ----- 總結篇Java設計模式
- spring cloud gateway之filter篇SpringCloudGatewayFilter
- ASP.NET MVC 幾種 Filter 的執行過程原始碼解析ASP.NETMVCFilter原始碼
- 你所不知道的ASP.NET Core進階系列(三)ASP.NET
- ASP.NET Core MVC 之模型(Model)ASP.NETMVC模型