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中使用
首先我們建立一個asp.net mvc2/3的項目,建立好以後, 直接運作為什麼通路localhost/home/index會傳遞給 HomeController中名為index的action(即HomeController類中的index方法)?怎麼實作的呢? 在我們建立的項目中,Global.asax檔案中有以下方法 注冊路由
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" )
View Code
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的定義
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
LogUserOperationAttribute
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以及視圖檔案
HomeController
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
CustomHandleErrorAttribute
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方法寫注冊代碼
注冊全局Filters
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomHandleErrorAttribute());//系統的邏輯錯誤通過這個filters來處理
filters.Add(new HandleErrorAttribute());
}
在HomeController中新加一個Action--->Error
HomeController
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。
作者: wolfram 原文連結