今天,我將開啟一個嶄新的話題:ASP.NET MVC框架的探討。首先,我們回顧一下ASP.NET Web Form技術與ASP.NET MVC的異同點,並展示各自在Web領域的優劣點。在討論之前,我對這兩種技術都非常熱衷,我個人覺得在實際的項目開發中,兩者都能讓我們受益匪淺,因此是目前Web領域兩大平行和流行的技術。我們都知道,在傳統的ASP.NET Web Form應用程序中,Microsoft已為我們設計了較為完整、簡潔的開發環境,並提供了復雜的處理引擎,開發者只需簡單的拖放控件和編寫對應的事件就能實現我們想要的功能。但是,正是因為這些快捷的開發模式,對於開發者而言,卻很難了解程序背后HTML的運作機制,而通常情況下HTML與后台程序又交織在一起(盡管比起ASP有較大進步),使得頁面設計人員和開發人員的職責融為一體,難以拆解,有時初學者對ASP.NET Web Form的機制不深入了解的話,會導致各種誤用,例如ViewState存放大量的數據造成頁面阻塞,Session跨服務器負載而丟失等莫名其妙的問題,而難以找到問題的根本點。因此ASP.NET MVC應運而生,將模型、視圖與控制器划分到不同的Web應用模塊中,使得設計人員與開發人員職責更好的分離,並且不需保存頁面視圖,不需數據回傳,提高了應用程序的可維護性和擴展性,特別是非常容易進行測試驅動開發。
接下來,我們來了解MVC的三大組件(模型、視圖、控制器)。所謂模型,就是MVC需要提供的數據源,負責數據的訪問和維護。所謂視圖,就是用於顯示模型中數據的用戶界面。所謂控制器,就是用來處理用戶的輸入,負責改變模型的狀態並選擇適當的視圖來顯示模型的數據。以下是我繪制的MVC三大組件之間的交互圖。


從界面中可以看出,Microsoft已約定了一套規范的目錄結構,下面我就每一個目錄的歸納一下:
Content: 存放CSS、圖片等靜態資源文件。
Controllers: 存放一系列以***Controllers命名的控制器組件,執行與模型和視圖的交互(一般業務會提取到業務邏輯層中)。
Models: 存放Linq to SQL或ADO.NET Data Entity Model等模型組件,也可存放有關的數據操作等(一般會提取到數據訪問層中)。
Scripts: 存放應用程序必須的Javascript文件。
Views: 存放與Controllers在與方法對應的視圖,注意:如果存在***Controllers控制器,在Views中就必然會映射一個***命名的文件夾。在Views之下的Shared則存放視圖組件的公共部分,如.master、樣式表等。
此外,Global.asax實現MVC的URL的路由控制,可以在其中的RegisterRoutes()中設置默認的路由,以下是Global.asax的具體路由代碼。
public class MvcApplication : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { // 可以忽略的路由配置 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // 設置默認路由 routes.MapRoute( "Default", // 路由名稱 "{controller}/{action}/{id}", // 帶有參數的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } }
當MVC在.net framework3.5中運行時,還需要在web.config中的httpModules配置節中注冊UrlRoutingModule類,用於解析URL路由,這正是MVC與傳統的ASP.NET應用程序的根本區別。
<httpModules> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </httpModules>
那UrlRoutingModule模塊是怎樣將頁面請求轉化為URL路由並調用控制器中的方法來實現視圖展現的呢?
(1). 當啟動MVC網站時,用戶輸入的URL地址將首先傳遞到UrlRoutingModule模塊,該模塊將解析該URL並選擇對應的URL路由,並得到對應的IHttpContext對象來處理該URL路由。在默認情況下,該IHttpContext對象就是MvcHandler對象,通過該對象來選擇相關的控制器來處理用戶的請求。即UrlRoutingModule模塊和MvcHandler對象是MVC網站的入口點,主要實現:選擇對應的控制器,實例化控制器對象,調用控制器的相關方法。以下表格更詳細地說明頁面請求的執行過程。

(2). 在傳統的ASP.NET應用程序中,每一個請求頁面對應文件系統的一個文件,這些頁面就是其實一個類,該類實現了IHttpHandler接口,每當頁面進行請求時將調用ProcessRequest()方法,將指定的內容返回到瀏覽器中。而在基於MVC的應用程序中,每一個請求頁面映射為相應控制器的相關方法,控制器負責將相關的內容返回到瀏覽器中。需要說明的是,同一個控制器可以包含多個方法來映射多個頁面。頁面到控制器的映射是通過路徑表(RouteTable.Routes)來實現的,在路徑表中添加對應的路由對象,實現將具有{controller}/{action}/{id}模式的URL映射到MvcRouteHandler。當請求MVC頁面時,web.config配置文件中的UrlRoutingModule模塊解析該URL,並獲取相關的RouteData對象來創建HttpHandler的實例MvcHandler,然后在調用MvcHandler的ProcessRequest()方法創建控制器的實例,再執行控制器的Execute()方法並通過反射機制指定要調用的View()方法來實現頁面內容向瀏覽器推送。
接下來,我將在剛才新建的MVC應用程序基礎上構建模型,來實現應用程序的數據訪問和業務邏輯(這里只是演示,不適合實際項目開發)。在"Models"文件夾上添加"ADO.NET實體數據模型"並配置相關的連接(這里以Northwind.edmx為例),選擇Categories表,操作完成后應該有如下截圖。
然后我們切換到控制器目錄,剛才我們談過,控制器將用戶請求的URL路由,分發到相關的方法而不是文件系統中的某個真實文件。打開默認的"HomeController.cs"文件:
//表示執行控制器方法出錯時將會打開友好的錯誤提示頁面 [HandleError] //控制器必須以"***Controllers"命名,並實現Controller或IController public class HomeController : Controller { //控制器中的方法必須是public,如果是內部方法,將被設置為[NonActionAttribute] public ActionResult Index() { //設置字典數據以便將指定數據傳遞到視圖 ViewData["Message"] = "Welcome, Miracle!"; //返回指定的內容到瀏覽器中 return View(); } }
我們修改HomeController.cs以便實現Categories的顯示、編輯、添加以及詳情頁面,對應相關的動作方法為Index()、Edit()、Create()以及Details()。
public class HomeController : Controller { //實例化數據實體對象 private Northwind db = new Northwind(); //數據顯示 public ActionResult Index() { var model = db.Categories.ToList(); return View(model); } //編輯 [AcceptVerbs(HttpVerbs.Get)] public ActionResult Edit(int id) { var model = db.Categories.First(c => c.CategoryID == id); return View(model); } [AcceptVerbs(HttpVerbs.Post)] public ActionResult Edit(int id, FormCollection form) { var model = db.Categories.First(c => c.CategoryID == id); UpdateModel(model, new[] { "CategoryName", "Description" }); db.SaveChanges(); return RedirectToAction("Index"); } //創建 [AcceptVerbs(HttpVerbs.Get)] public ActionResult Create() { var model = new Categories(); return View(model); } [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(int id, FormCollection form) { var model = db.Categories.FirstOrDefault(c => c.CategoryID == id); if (model == null) { var cate = new Categories(); UpdateModel(cate, new[] { "CategoryName", "Description" }); db.AddToCategories(cate); db.SaveChanges(); return RedirectToAction("Index"); } else { return RedirectToAction("Create"); } } //詳情 [AcceptVerbs(HttpVerbs.Get)] public ActionResult Details(int id) { var model = db.Categories.First(c => c.CategoryID == id); return View(model); } }
從以上的方法中我們看到返回類型都是ActionResult類,其實這是個抽象類,實際返回都是ActionResult的子類。

細心地朋友可能會發現,Edit、Create都是以成對的方式出現,這樣有什么意義呢?再往下看,不難發現其中一個使用Get,另一個使用Post。也就是說編輯之前我們首先會獲取這條數據,編輯完成后提交到服務器。而Create則是使用Get方式打開一個沒有值的頁面,填充對應的值之后再提交到服務器。這樣一想,不就簡單的多了。有了控制器,接下來,我們開始創建與控制器方法對應的視圖了,操作起來很簡單:點擊控制器方法名右鍵"添加視圖",將會再對應的Views對應的文件夾(名稱為控制器)下建立對應的頁面(名稱為方法名)。首先創建Index視圖(需要選擇強類型視圖MVCDemo.Models.Categories)和視圖內容為List,代碼如下:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MVCDemo.Models.Categories>>" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Index </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2> 類別列表</h2> <table border="1"> <tr> <th>操作 </th> <th> 類別編號 </th> <th> 類別名稱 </th> <th> 說明 </th> </tr> <% foreach (var item in Model) { %> <tr> <td> <%: Html.ActionLink("編輯", "Edit", new { id=item.CategoryID }) %> | <%: Html.ActionLink("詳情", "Details", new { id=item.CategoryID })%> </td> <td> <%: item.CategoryID %> </td> <td> <%: item.CategoryName %> </td> <td> <%: item.Description %> </td> </tr> <% } %> </table> <p> <%: Html.ActionLink("創建", "Create") %> </p> </asp:Content>
在這個頁面中,我們發現很多地方都是用<%***%>包含代碼的,還有Html這個內置類,其中包含很多方法用於創建控件。這里ActionLink(linkText,actionName,params)用於創建鏈接,linkText是鏈接文本,actionName是控制器的方法名稱,params是參數列表。以同樣方式創建編輯、新建及詳情頁面。這里我不再論述,可在源代碼中下載。唯一需要改動的就是選擇對應的視圖內容(Edit、Create、Details)。談到這里,對MVC已經有了基本的認識,如果對其中的還不太了解,可以試着打開源代碼仔細分析一下即可。