1、深入學習Routing
首先Routing的處於的位置,一個Http請求過來,Web容器接收到以后,將信息交給Routing(路由組件),Routing再進行處理~那么Routing的作用
確定Controller、確定Action、確定其他參數、根據識別出來的數據, 將請求傳遞給Controller和Action.
小提示:asp.net mvc預覽版的時候,Routing組件還是作為asp.net mvc的一部分,后續的版本似乎就微軟將其編譯成一個獨立的組件提供System.Web.Routing.dll,也就是說asp.net mvc項目是開源的,但是Routing組件並沒有開源。Routing組件不僅在asp.net mvc中可以使用,也可以在WebForm中使用

public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
Routes是Application級別(全局)的,在Application開始的時候,程序注冊路由,新建的項目默認只注冊了一條路由,看下代碼,
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
MapRoute第一個參數是此路由的名稱,第二個參數是URL的參數形式,{controller}相當於是一個string.Format方法中的占位符,意思是這里可以是任意一個controller名稱,
同理,action、id也是一樣的,那為什么請求的/Home/Index並沒有Id這個參數,第三個參數是路由規則默認值,這條路由默認的controller是home,action是index,而id呢,是可選的~~~當我們請求/Home/Index的時候,會被此路由獲取,而我們直接請求http://localhost的時候,可以到Home/Index的時候,路由參數有默認值
Ok,到這里我們學習了如何注冊路由,我們來試着自己寫一條路由

routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //自定義路由, routes.MapRoute( "myRoute", // Route name "{controller}-{action}", // URL with parameters new { controller = "Home", action = "Index" } // Parameter defaults ); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults );
然后重新啟動程序,因為Routes是Application級別的,在我們修改Application級別信息后,應該退出,否則不會有效果滴,這個是好多初學者容易犯錯的地方.如果是生產環境,應該重新啟動下Web容器.
我們運行程序以后,請求域名/home-index一樣可以請求頁面,說明我們自定義的路由有效.
這里路由的規則非常靈活,我們可以自定義,以下的路由規則都可以
routes.MapRoute(
"myRoute", // Route name
"{controller}-{action}-{1}-{2}-{3}", // URL with parameters
new { controller = "Home", action = "Index" } // Parameter defaults
);
MapRoute()方法
MapRoute有以下的重載方法
MapRoute( string name, string url);
MapRoute( string name, string url, object defaults);
MapRoute( string name, string url, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints);
MapRoute( string name, string url, object defaults, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);
name參數: 規則名稱, 可以隨意起名.不可以重名,否則會發生錯誤: 路由集合中已經存在名為“Default”的路由。路由名必須是唯一的。
url參數: url獲取數據的規則, 這里不是正則表達式, 將要識別的參數括起來即可, 比如: {controller}/{action} 最少只需要傳遞name和url參數就可以建立一條Routing(路由)規則.比如實例中的規則完全可以改為: routes.MapRoute( "Default", "{controller}/{action}");
defaults參數: url參數的默認值.如果一個url只有controller: localhost/home/ 而且我們只建立了一條url獲取數據規則: {controller}/{action} 那么這時就會為action參數設置defaults參數中規定的默認值. defaults參數是Object類型,所以可以傳遞一個匿名類型來初始化默認值: new { controller = "Home", action = "Index" } 實例中使用的是三個參數的MapRoute方法: routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults );
constraints參數: 用來限定每個參數的規則或Http請求的類型.constraints屬性是一個RouteValueDictionary對象,也就是一個字典表, 但是這個字典表的值可以有兩種: 用於定義正則表達式的字符串。正則表達式不區分大小寫。 一個用於實現 IRouteConstraint 接口且包含 Match 方法的對象。 通過使用正則表達式可以規定參數格式,比如controller參數只能為4位數字: new { controller = @"\d{4}"}
通過第IRouteConstraint 接口目前可以限制請求的類型.因為System.Web.Routing中提供了HttpMethodConstraint類, 這個類實現了IRouteConstraint 接口. 我們可以通過為RouteValueDictionary字典對象添加鍵為"httpMethod", 值為一個HttpMethodConstraint對象來為路由規則添加HTTP 謂詞的限制, 比如限制一條路由規則只能處理GET請求: httpMethod = new HttpMethodConstraint( "GET", "POST" )

routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" }, // Parameter defaults new { controller = @"\d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) } );
我們注冊這樣的一條路由

routes.MapRoute( "test", // Route name "{controller}-{action}-{id}", // URL with parameters new { controller = "Home", action = "Index" }, // Parameter defaults new { controller = @"^\w+", action = @"^\w+", id = @"^\d+" } );
編譯且運行,當我們請求/home-index-11可以請求到,而我們/home-index-1x這樣的是不能匹配的,
那這樣的規則的又有什么用處呢?在這里可以對請求進行過濾,比如我們的id只能是數字類型,防止一些非法檢測
路由組件的調試
假設我們寫了N條路由,當我們發送請求的時候,並沒有被我們想要的規則所捕獲解析,so..我們需要調試。
在我們的項目添加RouteDebug.dll的引用,並在Global.asax文件中的Application_Start方法添加以下代碼

protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); //啟動路由表調試 RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes); }
編譯后,我們啟動程序,會發現有一個Route Tester頁面,關於RouteDebug匹配的頁面信息,請大家查詢RouteDebug的手冊.
Route Tester中的Route Data請求就是當前請求的路由信息
我們請求/home/index,從圖中可以看到被第4條路由捕獲到.
那這樣的路由有什么作用呢?我想大部分做SEO的朋友都有經驗,二級頁面和三級頁面,爬蟲抓取的頻率顯然是不一樣的,這樣我們可以將三級甚至更深層的頁面構造成二級頁面~這個也是SEO的技巧之一
注意:我們在寫路由規則的時候,因為路由規則有前后順序(指注冊的先后順序),也許寫的路由規則被它前面的規則給捕獲到,進行處理。那后面注冊的路由就無效
2、Controller學習
在ASP.NET MVC中, 一個Controller可以包含多個Action. 每一個Action都是一個方法, 返回一個ActionResult實例.
ActionResult類包括ExecuteResult方法, 當ActionResult對象返回后會執行此方法.
Controller 處理流程:
1. 頁面處理流程 發送請求 –> UrlRoutingModule捕獲請求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest
2.MvcHandler.ProcessRequest() 處理流程: 使用工廠方法獲取具體的Controller –> Controller.Execute() –> 釋放Controller對象
3.Controller.Execute() 處理流程: 獲取Action –> 調用Action方法獲取返回的ActionResult –> 調用ActionResult.ExecuteResult() 方法
4.ActionResult.ExecuteResult() 處理流程: 獲取IView對象-> 根據IView對象中的頁面路徑獲取Page類-> 調用IView.RenderView() 方法(內部調用Page.RenderView方法)
Controller對象的職責是傳遞數據,獲取View對象(實現了IView接口的類),通知View對象顯示.
View對象的作用是顯示.雖然顯示的方法RenderView()是由Controller調用的,但是Controller僅僅是一個"指揮官"的作用, 具體的顯示邏輯仍然在View對象中.
注意IView接口與具體的ViewPage之間的聯系.在Controller和View之間還存在着IView對象.對於ASP.NET程序提供了 WebFormView對象實現了IView接口.WebFormView負責根據虛擬目錄獲取具體的Page類,然后調用 Page.RenderView()
Controller中的ActionResult
在Controller中,每一個Aciton返回都是ActionResult,我們通過查看ActionResult的定義

// Summary: // Encapsulates the result of an action method and is used to perform a framework-level // operation on behalf of the action method. public abstract class ActionResult { // Summary: // Initializes a new instance of the System.Web.Mvc.ActionResult class. protected ActionResult(); // Summary: // Enables processing of the result of an action method by a custom type that // inherits from the System.Web.Mvc.ActionResult class. // // Parameters: // context: // The context in which the result is executed. The context information includes // the controller, HTTP content, request context, and route data. public abstract void ExecuteResult(ControllerContext context); }
關於ActionResult的派生類,大家可以參考:http://blog.csdn.net/steven_husm/article/details/4641281
3、Filter的學習
mvc項目中,action在執行前或執行后想做一些特殊的操作,比如身份校驗、行為截取等,asp.net mvc提供了以下幾種默認的Filter
ASP.NET MVC 框架支持以下幾種篩選器:
1、授權篩選器– 實現了 IAuthorizationFilter 接口
這一類的篩選器用來實現用戶驗證和對Action的訪問授權。比如Authorize 就屬於Authorization 篩選器。
2、Action 篩選器– 實現了 IActionFilter 接口
它可以包含一些Action執行前或者執行后的邏輯,比如有一些篩選器專門用來修改Action返回的數據。
3、Result 篩選器– 實現了 IResultFilter 接口
它可以包含一些view result生成前或者生成后的邏輯,比如有一些篩選器專門用來修改視圖向瀏覽器展現前的結果。
4、異常篩選器– 實現了IExceptionFilter 接口
它用以用來處理Action或者Result的錯誤,也可以記錄錯誤。
篩選器的默認執行順序也和上面的列出的序號相同,比如Authorization 篩選器會先於Action 篩選器執行,而Exception 篩選器總會在最后執行。當然你也可以根據需要通過Order屬性設定篩選器執行的順序。
下面通過一個實際的例子來說明應用,新建一個mvc3項目,在項目中新加一個Common文件夾,並新加一個類LogUserOperationAttribute.cs

public class LogUserOperationAttribute : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext filterContext) { //todo寫日志代碼,這里注意並發性 File.AppendAllText(@"C:\log.txt", string.Format("{0}日志", DateTime.Now)); base.OnResultExecuted(filterContext); } }
新加一個HomeControllers,並在HomeControllers中新加一個Action--Index以及視圖文件

public class HomeController : Controller { // // GET: /Home/ [LogUserOperation] public ActionResult Index() { return View(); } }
然后保存代碼,F5運行,當頁面顯示出來以后,在C盤已經有log.txt文件.
abstract class ActionFilterAttribute這四個方法分別代表,Action執行時,Action執行后,Result返回時,Result返回后。(它們的執行順序跟下圖一致)
當然,我們也可以在ASP.NET MVC 3.0中增加Global Action Filter,這個就把此類filter變成全局的filter,所有Controller的action都會通過個filter的規則來執行,它跟我們在Controller或某個action所標識的屬性有很大的區別,就是全局跟部分的區別。對於Global Action Filter 有着很多的應用,比如系統的權限、系統異常的處理
Gloable Filter實際應用--系統異常處理體系
我們程序運行過程中會有各種不可預料的情況,執行某個Action會發生異常,那我們異常信息需要記錄下來,我們可以像上面的例子一樣,在Action或Controller上打上標記,但是那么多action和Controller都去打標記,確實是很痛苦的事情,而asp.net mvc3有一個全局的Filter,我們只需要將我們自定義的Filter注冊成全局的Filter
在Common文件夾中,新建一個類CustomHandleErrorAttribute.cs

public class CustomHandleErrorAttribute : IExceptionFilter { public void OnException(ExceptionContext filterContext) { File.AppendAllText(@"C:\error.txt", string.Format("{0}錯誤{1}", DateTime.Now, filterContext.Exception.Message)); } }
在Global.asax文件中,RegisterGlobalFilters方法寫注冊代碼

public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomHandleErrorAttribute());//系統的邏輯錯誤通過這個filters來處理 filters.Add(new HandleErrorAttribute()); }
在HomeController中新加一個Action--->Error

public class HomeController : Controller { // // GET: /Home/ [LogUserOperation] public ActionResult Index() { return View(); } public ActionResult Error() { try { System.IO.File.Open("C:\\111111.exe", System.IO.FileMode.Open); } catch (Exception ex) { throw ex; } return View(); } }
編譯后運行項目~我們請求/Home/Error這個action,,程序出現異常,我們切換到C盤,發現C盤已經有error.txt文件
注冊到全局的Filters所有的Action和Result執行前后都會調用我們的CustomHandleErrorAttribute的重寫的方法。
GlobalFilters、ControllerFilters、ActionFilters的執行順序問題
GlobalFilters-->ControllerFilters-->ActionFilters《這個是有執行的前置條件的》
當然這是在CustomHandleErrorAttribute類的定義上打上標記[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]的前提下。不然 如果Action打上了標簽跟Controller的相同則它只會執行Action上的Filter。