一、天降神器“剃鬚刀” — Razor檢視引擎
1.1 千呼萬喚始出來的MVC3.0
在MVC3.0版本的時候,微軟終於引入了第二種模板引擎:Razor。在這之前,我們一直在使用WebForm時代沿留下來的ASPX引擎或者第三方的NVelocity模板引擎。
Razor在減少程式碼冗餘、增強程式碼可讀性和Visual Studio智慧感知方面,都有著突出的優勢。Razor一經推出就深受廣大ASP.Net開發者的喜愛。
1.2 Razor的語法
(1)Razor檔案型別:Razor支援兩種檔案型別,分別是.cshtml 和.vbhtml,其中.cshtml 的伺服器程式碼使用了c#的語法,.vbhtml 的伺服器程式碼使用了vb.net的語法。
(2)@字元:@是Razor中的一個重要符號,它被定義為Razor伺服器程式碼塊的開始符號。例如,我們可以在View中直接寫C#程式碼輸出日期
1 |
1 <p>@DateTime.Now.ToString()</p> |
1.3 Razor語句塊
(1)在Razor檢視引擎中,我們可以使用@{code}來定義一段程式碼塊。
(2)Razor支援程式碼混寫:在程式碼塊中插入HTML、在HTML中插入Razor語句都是可以的。例如,我們可以使用@來作for迴圈,還可以進行if判斷
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@for (int i = 0; i < 10; i++) { <p>@i</p> } @if (ViewData.Count > 0) { <p>ViewData有資料</p> ViewData["Key"] = "Edison Chou"; } else { <p>ViewData暫無資料</p> } |
1.4 Razor頁面輸出特殊字串
與在ASPX試圖引擎中類似,如果要輸出特殊字串,還是藉助HtmlHelper類提供的擴充套件方法來實現。
(1)輸出原生的字串:@Html.Raw(html)
1 |
@Html.Raw("<h1>Razor</h1>") |
PS:預設的@會解析掉html程式碼
(2)還可以通過使用HtmlString型別和MvcHtmlString型別字串輸出原生包含HTML的字串
1 2 3 4 |
@{ IHtmlString html = new HtmlString("<span style='color:red'>哈哈,我是Razor剃鬚刀!</span>"); Response.Write(html); } |
1.5 Razor中的註釋
Razor伺服器端註釋為:@* 註釋內容 *@
1 |
@*<p>你好,Razor引擎!</p>*@ |
1.6 Razor中轉換資料型別
在Razor中提供了很多方便我們進行資料型別轉換的方法以及型別判斷的方法,如下圖所示:
例如,我們可以在View中對一個字串進行判斷和轉換:
1 2 3 4 5 |
@{ string test = "Edison Chou"; <p>@test.IsInt()</p> <p>@test.AsInt()</p> } |
二、Controller深入詳解
2.1 控制器的三個職責
(1)處理跟使用者的互動
(2)處理業務邏輯的呼叫
(3)指定具體的檢視顯示資料,並且把資料傳遞給檢視
2.2 控制器的三個約定
(1)必須是非靜態類
(2)必須實現IController介面
(3)必須是以Controller結尾命名
2.3 無所不能的Action
首先,在一個Controller中可以包含多個Action. 每一個Action都是一個方法, 返回一個ActionResult例項。那麼,這個ActionResult是什麼東東呢?
由微軟給出的註釋可以知道,ActionResult是一個操作方法的結果,並且是一個抽象類,那麼,也就代表了可以有多重結果的實現。這樣就解釋了,我們在Action中可以不僅可以返回ViewResult還可以返回JsonResult的原因。通過下表,我們可以清晰地看到,ActionResult的各種派生類的詳情:
從表中可以看出,我們所常用的各種XXXXResult都不約而同地繼承了ActionResult這個基類,或者是其父類(例如:ViewResultBase)繼承了ActionResult這個基類。因此,我們既可以在Action中返回檢視,還可以返回檔案流、重定向、空內容等結果。特別是,以前我們在WebForm時代常常與瀏覽器互動採用JSON格式的資料,需要使用JavaScriptSerializer這個類進行Serialize後返回。但是,在MVC的Action中,微軟已經幫我們封裝了好了JsonResult,因此,我們可以高興地感慨:返回Json,So Easy!
2.4 ActionResult用法
這裡只介紹幾個最常用的Result用法:
(1)EmptyResult:當使用者有誤操作或者是圖片防盜鏈的時候,這個EmptyResult就可以派上用場,返回它可以讓使用者啥也看不到內容,通過訪問瀏覽器端的原始碼,發現是一個空內容;
1 2 3 4 |
public ActionResult Empty() { return new EmptyResult(); } |
(2)Content:通過Content可以向瀏覽器返回一段字串型別的文字結果,就相當於Response.Write(“xxxx”);一樣的效果;
1 2 3 4 5 |
public ActionResult ContentResultDemo() { string contentString = "Hello Edison Chou!"; return Content(contentString); } |
(3)File:通過File可以向瀏覽器返回一段檔案流,主要用於輸出一些圖片或檔案提供下載等;
1 2 3 4 5 6 |
public ActionResult FileStreamResultDemo() { FileStream fs = new FileStream(Server.MapPath(@"/Content/programmer.jpg"), FileMode.Open, FileAccess.Read); return File(fs, @"image/gif"); } |
(4)HttpUnauthorizedResult:通過HttpUnauthorizedResult可以向瀏覽器輸出指定的狀態碼和狀態提示,如果不指定狀態碼,則預設為401無權訪問;
1 2 3 4 |
public ActionResult HttpUnauthorizedResultDemo() { return new HttpUnauthorizedResult(); } |
(5)Redirect與RedirectToAction:重定向與重定向到指定Action,我一般使用後者,主要是向瀏覽器傳送HTTP 302的重定向響應;
1 2 3 4 5 6 7 8 9 |
public ActionResult RedirectResultDemo() { return Redirect(@"http://localhost:23531/Home/ContentResultDemo"); } public ActionResult RedirectToRouteResultDemo() { return RedirectToAction("FileStreamResultDemo", "Home"); } |
(6)Json:通過Json可以輕鬆地將我們所需要返回的資料封裝成為Json格式,進行Ajax開發可以變得so easy!
1 2 3 4 5 |
public ActionResult JsonResultDemo() { var tempObj = new { Controller = "HomeController", Action = "JsonResultDemo" }; return Json(tempObj, JsonRequestBehavior.AllowGet); } |
(7)JavaScript:可以通過JavaScriptResult向瀏覽器單獨輸出一段JS程式碼,不過由於主流瀏覽器都對此進行了安全檢查,因此你的JS程式碼也許無法正常執行,反而是會以字串的形式顯示在頁面中;
三、Routing深入詳解
首先,ASP.Net MVC專案是URL請求驅動的,為什麼訪問localhost/home/index會傳遞給HomeController中名為index的action(即HomeController類中的index方法)?下面,我們一一來看下。
3.1 Routing的作用
假如有一個請求:localhost/home/index,那麼路由需要做的事情如下:
(1)確定Controller
(2)確定Action
(3)確定其他引數
(4)根據識別出來的資料,將請求傳遞給Controller和Action
3.2 神奇的路由規則
根據路由的作用,我們可以知道它是一個“指路人”,指示我們的請求應該到達哪個Controller中的Action。那麼,它是根據什麼規則來指路的呢?我們可以在App_Start資料夾中的RouteConfig類中找到這個神奇的規則是如何制定的。
1 2 3 4 5 6 7 8 9 |
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } |
(1)首先,第一句routes.IgnoreRoute代表對所有axd的資源訪問請求進行忽略,直接進行URL訪問;這裡可以閱讀參考資料第(5)篇,瞭解其詳細含義,這裡就不再贅述;
(2)然後,第二句開始使用MapRoute方法對整個網站定義了一個路由識別規則,這個規則的name是Default,url規則為:{controller}/{action}/{id}。例如我們要訪問的URL為:localhost/home/index,在這個URL中,localhost是域名, 所以首先要去掉域名部分: home/index,也是就對應了上面程式碼中的這種URL結構: {controller}/{action}/{id}。正是因為我們建立了這種URL結構的識別規則,,所以能夠識別出 Controller是home, action是index, id沒有則為預設值””。
(3)在MapRoute方法中為所有URL請求定義了一個defaults預設值:controller為空則指向Home,action為空則指向Index,而id則是可選的,非必須要的。
這裡,對於路由規則需要注意的有兩點:
(1)可以有多條路由規則;
(2)路由規則是有順序的(前面的規則被匹配後,後面的規則就不再匹配);
我們可以在RegisterRoutes這個方法中新增一條自定義路由規則,並取名為Default2,具體規則程式碼如下:
1 2 3 4 5 |
routes.MapRoute( name: "Default2", url: "{controller}-{action}-{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); |
這下如果我們以:localhost/Home-Index來訪問時,我們原本想要的是根據Default2這個路由規則訪問Home控制器下的Index這個Action,但卻被告知以404提示:
這是為什麼呢?我們再來看看RegisterRoutes這個方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default2", url: "{controller}-{action}-{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } |
我們剛剛提到,路由規則是有順序的(前面的規則被匹配後,後面的規則就不再匹配)。那麼,可以推斷,由於Default2在Default之後,有可能我們的請求localhost/Home-Index已經被Default這個規則所匹配了,因此Default2規則根本沒有出場Show一下。那麼,在Default規則中,它將Home-Index作為Controller的名字匹配,去訪問Home-Index這個Controller,而Action使用預設的Index,那麼它所請求的應該是這個URL:/localhost/Home-Index/Index。由於網站中,並沒有Home-Index這個Controller,所以也就出現了剛剛那個404頁面。
3.3 MapRoute方法介紹
(1)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);
我們在上面所使用的便是第二種過載。
(2)MapRoute方法引數詳細介紹:
①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” }。
在ASP.Net MVC網站預設例項中使用的是三個引數的MapRoute方法:
1 2 3 4 5 |
routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); |
④constraints引數:
用來限定每個引數的規則或Http請求的型別。constraints屬性是一個RouteValueDictionary物件,也就是一個字典表,但是這個字典表的值可以有兩種型別:
一是:用於定義正規表示式的字串(正規表示式不區分大小寫)。通過使用正規表示式可以規定引數格式,比如controller引數只能為4位數字:new { controller = @”\d{4}”}
1 2 3 4 5 6 |
routes.MapRoute( name: "Default2", url: "{controller}-{action}-{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = @"\d{4}" } ); |
二是:一個用於實現 IRouteConstraint 介面且包含Match方法的物件。
例如:通過第IRouteConstraint 介面可以限制請求的型別(是GET還是POST)。因為System.Web.Routing中提供了HttpMethodConstraint類,,這個類實現了IRouteConstraint 介面。
我們可以通過為RouteValueDictionary字典物件新增鍵為”httpMethod”, 值為一個HttpMethodConstraint物件來為路由規則新增HTTP 謂詞的限制,比如限制一條路由規則只能處理GET請求:httpMethod = new HttpMethodConstraint( “GET” )
1 2 3 4 5 6 7 8 |
routes.MapRoute( name: "Default2", url: "{controller}-{action}-{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = @"\d{4}", httpMethod = new HttpMethodConstraint("GET") } ); |
3.4 URL路由例項詳解
一般來說,對於一個網站為了SEO友好,網址的URL層次最好不要超過三層:localhost/{頻道}/{具體網頁},其中域名第一層, 頻道第二層, 那麼最後的網頁就只剩下最後一層了。如果使用預設例項中的“{controller}/{action}/{其他引數}”的形式則會影響網站的SEO。
假設我們有一個綜合型服務網站,其中有租房頻道、酒店頻道、KTV頻道、電影院頻道等等。我們應該怎樣來設計URL路由規則呢?
(1)首先,我們知道:可以有多條路由規則,但是路由規則是有順序的(前面的規則被匹配後,後面的規則就不再匹配);所以,我們可以定義多條路由規則,粒度細的模組(比如:具體的酒店列表頁面)路由規則放最前面,粒度粗的模組(比如:入口網站的首頁)路由規則放在最後面。
(2)其次,根據模組粒度劃分層次結構,以粒度粗細排序為:網站首頁->頻道首頁->具體內容;
(3)最後,我們可以看一個具體的URL路由例項來分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 酒店列表頁匹配 routes.MapRoute( "酒店列表頁", "hotels/{action}-{city}-{price}-{star}", new { controller = "Hotel", action = "list", city = "beijing", price = "-1,-1", star = "-1" }, new { city = @"[a-zA-Z]*", price = @"(\d)+\,(\d)+", star = "[-1-5]" } ); // 酒店頻道所有匹配 routes.MapRoute( "酒店首頁", "hotels/{*iiii}", new { controller = "Hotel", action = "default", hotelid = "" } ); // 網站首頁預設匹配 routes.MapRoute( "網站首頁", "{*values}", new { controller = "Home", action = "index" } ); |
(4)我們可以分析一下上面的路由規則所實現的功能:
①訪問 www.mywebsite.com/hotels/list-chengdu-100,200-3 會訪問酒店頻道的列表頁,並傳入查詢引數(price為100,200,star為3);
②訪問 www.mywebsite.com/hotels 下面的任何其他頁面地址,都會跳轉到酒店首頁;
③訪問 www.mywebsite.com 下面的任何地址,如果未匹配上面2條,則跳轉到首頁;
(5)根據上面的規則和實現的功能,我們可以做一個簡單的總結如下:
①Routing規則有順序(按照新增是的順序),如果一個url匹配了多個路由規則,則按照第一個匹配的路由規則執行。
②由於上面的規則,要將具體頻道的具體頁面放在最上方,將頻道首頁 和 網站首頁 放在最下方。
③{*values}表示後面可以使用任意的格式。
3.5 URL路由除錯
在ASP.Net MVC中,預設是不允許對路由規則進行除錯的。但是,我們可以通過使用RouteDebug來輔助進行除錯。
(1)首先,我們下載RouteDebug.dll到我們的專案中,並新增對其的引用。
(2)其次,在Global.asax中的Application_Start方法中新增一句程式碼:
RouteDeug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
1 2 3 4 5 6 7 8 9 10 |
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); RouteDeug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes); } |
(3)最後,F5除錯執行,我們請求localhost/Home-Index這個URL時,可以清楚地發現,系統將Home-Index匹配了第一條預設路由規則,也就是將Home-Index作為Controller的名稱進行匹配,這也就證明了為什麼我們輸入這個請求不會匹配第二條Default2的路由規則出現剛剛那個404頁面了。
參考資料
(1)馬倫,《ASP.Net MVC視訊教程》,http://bbs.itcast.cn/thread-26722-1-1.html
(2)葡萄城控制元件技術團隊,《ASP.NET MVC 5—控制器》,http://www.cnblogs.com/powertoolsteam/p/aspnet-mvc5-controller.html
(3)李亮,《ASP.Net MVC3 Controller》,http://www.cnblogs.com/wlitsoft/archive/2012/05/28/2520799.html
(4)顧裡江,《ActionResult解析》,http://blog.csdn.net/gulijiang2008/article/details/7642213
(5)紫魚Tiler,《MVC路由中routes.IgnoreRoute》,http://www.cnblogs.com/flyfish2012/archive/2013/02/01/2889184.html
(6)Capricornus,《路由匹配檢測元件RouteDebug.dll》,http://www.cnblogs.com/Capricornus/archive/2010/08/26/1808907.html