0. 前言
正如標題「7 天玩兒轉 ASP.NET MVC」所言,這是個系列文章,所以將會向大家陸續推出 7 篇。設想一下,一天一篇,你將從一個愉快的周一開始閱讀,然后在周末成為一個 ASP.NET MVC 開發者,這很酷吧!
第一天是熱身運動,這篇我們將圍繞 Controller 和 Views 實踐兩個 Labs。在每個 Lab 之中都伴隨着一些 Question 和 Answer。所以文章的主體框架是 Lab 和 Q&A。
1. 開始之前的准備
我們只需要 Visual Studio 工具就可以開始 ASP.NET MVC 之旅。你可以通過 Visual Studio 官網 下載所需版本。
2. MVC vs Webforms
許多 ASP.NET 開發者第一次接觸 MVC 時會認為它是區別於 Webforms 的,會認為它是一個全新的技術。確實如此,如果說 ASP.NET Webforms 是一個創建 Web Application 的框架,那么 ASP.NET MVC 就是一個更棒的架構體系,它以一種更合理的方式來組織和放置我們的代碼。
不可否認的是,ASP.NET Webforms 在過去十多年都非常流行,從 VB 開始,微軟就開始傳經布道 RAD 和 Visual Programming 的方法。連微軟的開發工具都稱作 Visual Studio,可見一斑。
通過使用 Visual Studio,開發者能通過拖拽的方式將 UI 構件放置到設計界面,Visual Studio 便自動為這些構件產生 C# 或者 VB.NET 代碼。這些可被稱作為「Code Behind」 ,在「Code Behind」區域內,開發者可以來寫一些邏輯代碼。
所以微軟的 Visual RAD 方法實際上就是兩件事,UI 和 Code Behind。例如 ASP.NET Webforms,有 ASPX 和 ASPX.CS;對於 WPF,有 XAML 和 XAML.CS,不一而足。
3. ASP.NET Web Forms 的問題
既然 ASP.NET Webform 如此成功,為什么微軟還要考慮創建 ASP.NET MVC 呢?主要原因是出在 ASP.NET Webform 的性能上。可以從兩個性能角度考慮:
- 響應時間:Server 端響應請求的時間是多少?
- 帶寬消耗:多少數據被傳輸?
我們來嘗試解釋為什么 ASP.NET Webforms 的響應時間很慢。通過一個小的負載測試,我們發現 ASP.NET MVC 比 ASP.NET Webforms 快2倍左右。
假如 ASPX 有這樣一段關於 Text Box 的簡單代碼。
<asp:TextBox ID="TextBox1" runat="server">
然后為 Text Box 寫一些后台邏輯代碼,為它進行賦值和背景色的操作。
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Text = "Make it simple";
TextBox1.BackColor = Color.Aqua;
}
運行程序后,將會在 HTML 頁面看到輸出。
如果你查看 HTML 的源代碼,它是這樣的:
<input name="TextBox1" type="text" value="Make it simple" id="TextBox1" style="background-color:Aqua;" />
現在停止閱讀,閉上眼睛思考片刻:
- 這真的是一個創建 HTML 的高效方式嗎?我們真的需要開始這樣一個漫長的服務器響應之旅而只是為了在瀏覽器上顯示如此簡單的頁面嗎?
- 難道開發者們不能直接寫 HTML 頁面嗎,難道很難寫?
事實上,每一次請求都會有一次 Conversion 邏輯在運行,它用於轉換 HTML 輸出的控件。當我們輸出的控件是一些 Grids 表格, Tree View 樹形控件等一些復雜的 HTML 頁面時,這種轉換將會變得更耗時,並且非常復雜,使得等待時間更長。
為了解決這個問題,開始拋棄「Code Behind」吧,寫一些純凈的 HTML。
長期從事 ASP.NET 的開發者一定非常熟悉 Viewstate,它能夠自動保存 post 返回的狀態並且減少了開發時間。但是正是由於這種開發時間的減少帶來了巨大的代價,Viewstate 增加了頁面的大小。通過負載測試,對比 ASP.NET MVC,我們發現 Viewstate 增加了近兩倍的頁面大小。
大小的增加是由於 Viewstate 產生了額外的字節。下圖是 Viewstate 的截圖快照。也許有人會反駁放棄 Viewstate 的觀點,但是對於開發者而言,如果有其它選擇,他們會嘗試其它選擇。
為了解決這個問題,開始拋棄 Server 控件吧。
自定義 HTML
因為我們都是通過 ASP.NET 控件和后台代碼來編寫應用,所以我們沒有辦法來決定什么樣的 HTML 被輸出,也不知道它們的效率是如何的。例如我們可以看一段 ASPX 代碼,你可以試猜想一下什么樣的 HTML 將被產生。
<asp:Label ID="Label1" runat="server" Text="I am label">
<asp:Literal ID="Literal1" runat="server" Text="I am a literal">
<asp:Panel ID="Panel1" runat="server" Text="I am a panel">
Label 控件會生成 DIV 還是 SPAN 標簽?如果你運行一下代碼會發現,Label 控件被轉換為一個 SPAN,Literal 控件被轉換為一個簡單的 Text,而 Panel 控件則被轉換為一個 DIV。
<span id="Label1">I am label</span>
I am literal
<div id="Panel1">I am panel</div>
因此與其使用 Server 控件來生成 HTML,倒不如直接手寫一些 HTML 來實現一些 HTML 控件。
所以,解決方案便是,不要再使用 Server 控件,直接編寫一些 HTML。
直接編寫 HTML 也帶來一些好處,這使得 Web 設計者能夠和開發團隊緊密工作。Web 設計者可以使用 DreamWeaver 或者 FrontPage 來獨立設計並獲取 HTML 代碼。如果我們使用了服務器控件,這些設計工具便不能很好地識別。
后台代碼可重用性
如果你看到過專業的 ASP.NET Webform 項目,你會發現它的后台代碼經常會囊括很大的代碼量,並且代碼真的很復雜。這些代碼繼承「System.Web.UI.Page」類,而這個類也不是一個常規的類,它不可以被重用或者實例化。換言之,你永遠無法在一個 Webform 類里做這樣的事情:
WebForm1 obj = new WebForm1();
obj.Button1_Click();
因為「WebForm」類沒有「Request」和「Respnse」對象是不能夠被實例化的。可以從下面的代碼中看到「WebForm」的「ButtonClick」事件代碼,從這段代碼中你就可以看到為什么實例化會很難實現。
protected void Button1_Click(object sender, EventArgs e)
{
// The logic which you want to reuse and invoke
}
單元測試
基於之前所說的后台代碼無法直接實例化,所以可想而知的是,單元測試或自動化測試都是非常困難的。開發者只能自動運行應用進行手動測試。
4. 解決方案
通過之前的分析,我們發現 ASP.NET Webforms 的兩個關鍵因素:「Code Behind」和「Server Controls」,即后台代碼和服務器控件。它們影響的代價和解決方案如下圖所示:
我們需要的解決方案便是將后台代碼遷移到獨立的簡易類庫,並且放棄 ASP.NET Server 控件,寫一些簡單的 HTML 頁面。
簡而言之,解決方案就像如下圖的形象說明,將 Web Form「減肥」為 MVC。
5. ASP.NET MVC 如何解決 Webforms 的問題
正如我們之前所討論的,后台任務和服務器控件是問題的根源。所以如果你看一下當前的 Webform 架構體系就會發現,開發者使用的幾乎就是 3 層架構體系。
這 3 層架構體系包含了 UI,這個 UI 實際上包含了 ASPX 和 后台代碼。
ASP.NET MVC 包含了三部分,即 Model,View 和 Controller。Controller 負責后台邏輯代碼。View 是純凈的 HTML 頁面。Model 是中間數據層。
這里有兩個主要的改變,其一是 View 變為簡單的 HTML 頁面,其二是后台代碼轉換為簡單的 .NET 類,我們稱之為 Controller。
ASP.NET MVC 的請求流如下:
-
第一步:首先觸發 Controller。
-
第二步:依據行為 Controller 創建 Model 對象。Model 反過來通過調用數據接口層來向 Model 對象填充數據。
-
第三步:填充完的 Model 對象將數據傳輸給 View 層,然后展示出來。
現在我們已經理解 ASP.NET MVC 的各個組件了。接下來開始深入學習每個組件,並且做一些小的 Lab。我們將從 Controller 開始,因為它是 MVC 架構體系的核心。
6. 如何理解ASP.NET MVC 中的 Controller
為了理解Controller,我們首先要理解「用戶交互邏輯」的概念。
什么是交互邏輯?
場景 1
你是否仔細想過,當終端用戶在瀏覽器上敲下一個 URL 並按下回車后,會發生什么事情?
瀏覽器發送一個請求給服務器,服務器再做出響應。
通過這種方式的請求,客戶端嘗試與服務器進行交互。服務器能夠返回響應是因為服務器已經有了一些邏輯來處理這些請求。這個邏輯實際上承載了用戶的請求以及用戶與服務器的交互行為,這便是用戶交互邏輯。
場景2
存在這樣一種可能,服務器返回的響應是一個 HTML 響應。這個 HTML 包含了一些輸入框或者提交按鈕組件。
當點擊「SaveCustomer」按鈕時,會發生什么事情?
如果你的回答是「一些事件處理器來處理這個按鈕點擊」,那么就錯了。
現實是 Web 編程是沒有事件的概念的。一種情況是,微軟為 ASP.NET Webforms 為我們寫了一些代碼,並給人一種事件驅動編程的感覺。實際上,這只是一個錯覺或者幻想。
當按鈕被點擊后,一個簡單的 HTTP 請求就被發送到服務器上。這次不同的是,「Customer Name」,「Address」和「Age」的值都將伴隨着請求被發送。從根本上說,如果發生了請求,那么服務器就會根據已經寫好的邏輯發出返回響應,簡而言之,服務器上一定存在一些用戶交互邏輯。
在 ASP.NET MVC 中,最后一個字母「C」就代表的是 Controller,它是用於處理用戶交互邏輯的。
7. Lab1 - 從簡單的 MVC Hello World 來理解 Controller
創建 ASP.NET MVC 5 程序
打開 Visual Studio 2013 或更高版本,點擊 文件 > 新建 > 項目。
選擇 Web 應用,填寫應用名稱,選擇應用路徑,點擊確定。
選擇 MVC 模板文件
點擊更改權限,在對話框中選擇「無權限」。
點擊確定即可。
創建 Controller
在資源管理器中,右擊「Controller」文件夾,點擊 添加 > 控制器。
選擇 MVC 5 控制器,點擊添加。
將控制器命名為「TestController」,然后點擊添加。
有一點非常重要,不要刪除「Controller」這個單詞,它是控制器的關鍵字。
創建行為方法
打開剛剛創建的「TestController」類,你會發現里面有一個方法叫「Index」,將這個方法刪除,然后創建一個新的公開方法,稱為「GetString」。
public class TestController : Controller
{
public string GetString()
{
return "Hello World is old now. It's time for wassup bro ;)";
}
}
執行並測試
按下 F5。在地址欄中以「ControllerName/ActionName」的格式輸入。但是需要注意的是,不要將控制器的名稱「Controller」加上,直接寫「Test」即可。
8. 圍繞 Lab 1 的 Q&A
TestController 和 Test 的關系?
「TestController」是一個類的名稱,然而「Test」確是一個控制器的名稱。需要注意的是,當你在 URL 中輸入控制器的名稱時,不要帶上單詞「Controller」。
什么是 Action「行為」方法?
Action 方法是一個在控制器里的簡單公共方法,它負責接收用戶的請求,並返回一些回應。通過上述的例子,可以看到行為方法「GetString」是返回一個字符串回應。
注意:在 ASP.NET Webforms 里,默認返回的都是 HTML。有一種情形是,如果我們想返回一些其它非 HTML 類型的請求,就得要創建 HTTP 處理器,重寫內容類型,然后做出回應。這並不是一個簡單的任務。但是在 ASP.NET MVC 中是很容易的。你像返回「String」就返回一個「String」,而不需要返回一個完整的 HTML 頁面。
如果在一個 Action 方法中,返回是一個對象,會發生什么?
查看以下代碼。
namespace WebApplication1.Controllers
{
public class Customer
{
public string CustomerName { get; set; }
public string Address { get; set; }
}
public class TestController : Controller
{
public Customer GetCustomer()
{
Customer c = new Customer();
c.CustomerName = "Customer 1";
c.Address = "Address1";
return c;
}
}
}
輸出 Action 方法如下。
當返回的類型是「Customer」這樣的對象時,它會返回對象的實現方法 「ToString()」。方法「ToString()」 默認返回類的全名,即 「NameSpace.ClassName」這樣的形式。
如果想得到上述例子的屬性值該如何操作?
直接重寫類的方法「ToString」即可。
public override string ToString()
{
return this.CustomerName+"|"+this.Address;
}
按下 F5,查看輸出結果。
Action 方法前一定要加上 public 修飾符嗎?
答案是肯定的,Action 方法都自動加上 Public 修飾符。
非 Public 方法如何理解?
這些非 Public 方法只是類內部的非公開方法,簡單理解就是它們不能被 Web 調用。
如果我們想實現一個 Public 的非 Action 方法,該如何去做?
只是簡單地加上 NonAction 屬性即可。
[NonAction]
public string SimpleMethod()
{
return "Hi, I am not action method";
}
當我們試着調用如下 Action 方法,會得到如下的響應。
9. 理解 ASP.NET MVC 中的 Views
就像我們剛才理解的 Controller 是用於處理用戶的請求並返回響應。大多數情況下響應的是一個 HTML 頁面,瀏覽器能夠很好地理解這種格式。 HTML 有一些圖片,文本,輸入控件等。一般來說,在技術領域定義用戶接口設計的層稱為 UI 層,在 ASP.NET MVC 中稱為 View。
10. Lab2 - 演示 Views
在 Lab1 中我們創建了一個簡單的 MVC 應用,只有一些 Controller 和一些簡單的返回值。現在我們為 MVC 添加 View 的部分吧。
創建一個 Action 方法
在 TestController 中增加一個 Action 方法。
public ActionResult GetView()
{
return View("MyView");
}
創建一個 View
右擊剛才的 Action 方法,選擇「Add View」。
在「Add View」對話框中添加一個視圖,命名為「MyView」,取消「Use a layout」復選框,並點擊「Add」。
在解決方案瀏覽器下,就會發現「Views/Test」文件夾增加了一個新的視圖。
打開「MyView.cshtml」文件,將會看到內容如下。
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MyView</title>
</head>
<body>
Welcome to MVC 5 Step by Step learning
<body>
</html>
測試並運行
按下 F5,執行應用程序
11. 圍繞 Lab 2 的 Q&A
為什么視圖被放到了 Test 文件夾下?
在 ASP.NET MVC 中,Views 總是與特定的 Controller 相關聯被放到一個特殊的文件夾中。這個特殊的文件夾將以 Controller 的名字命名並且放到 Views 文件夾中(它位於根文件夾)。對於每一個 Controller 要訪問的視圖而言,只有放在正確的文件夾中才是可用的。
例如:關聯 TestController 的所有視圖都將被放置在「~/Views/Test」下,並且 TestController 只能訪問 Test 文件夾下的視圖。
對於多個控制器而言,可以重用一些視圖嗎?
答案是肯定的。我們將這些視圖文件放到一個指定的文件夾下,即「Shared」。
在「Shared」文件夾下的視圖文件能夠被所有控制器共享。
一個 Action 方法可以引用多個 Views 嗎?
答案是肯定的。如下代碼:
public ActionResult GetView()
{
if(Some_Condition_Is_Matching)
{
return View("MyView");
}
else
{
return View("YourView");
}
}
View 功能的目的是什么?
創建 ViewResult 對象,用於視圖做出回應。
- ViewResult 內部創建 ViewPageActivator 對象
- ViewResult 選擇正確的 ViewEngine,並將 ViewPageActivator 對象作為參數傳輸給 ViewEngine 構造函數。
- ViewEngine 創建 View 類的對象。
- ViewResult 調用 View 類的 RenderView 方法。
ActionResult 和 ViewResult 之間的關聯?
ActionResult 是一個抽象類,而 ViewResult 是 ActionResult 的多層子類。多層是因為 ViewResult 是 ViewResultBase 的子類,而 ViewResultBase 是 ActionResult 的子類。
如果我們想要返回 ViewResult,為什么定義的返回類型是 ActionResult?
這是為了實現多態,看如下例子:
public ActionResult GetView()
{
if(Some_Condition_Is_Matching)
{
return View("MyView");
}
else
{
return Content("Hi Welcome");
}
}
在上面的例子中,一些場景下,我們調用「View」函數將返回 ViewResult 類型,而一些其它場景下,我們調用「Content」函數用於放回內容結果。
什么是 ContentResult?
ViewResult 呈現了一個完整的 HTML 響應而 ContentResult 呈現的時一個純文本響應。就像返回一個純 String 類型意義。所不同的是,ContentResult 是一個 ActionResult 類型 ,包裝String 結果。ContentResult 也是 ActionResult 的子類。
可以無參調用 View 函數嗎?
答案是可以的。View 函數會通過當前的「ActionName」來找視圖。
第 2 天我們將會學到什么?
第 2 天中,我們將討論 Models,Validation,Jquery 以及 Json。所以,讓我們一起搖擺,一起學習吧!