前言
本篇博文主要介紹ASP.NET MVC中的三個核心元素:控制器、視圖與模型,以下思維導圖描述了本文的主要內容。
控制器
控制器簡介
在介紹控制器之前,簡單的介紹一下MVC工作原理:URL告知路由機制該使用哪個控制器(Controller),調用該控制器中的哪個方法(Action),並為該方法提供需要的參數。控制器響應用戶的輸入,在響應時修改模型(Model),並決定使用哪個視圖(View),並對該視圖進行渲染。
注意:MVC模式提供的是方法調用結果,而不是動態生成的頁面。
以上內容對於初學者來說可能不太理解,不過沒關系,經過后面的學習,待我們對MVC的整體架構有了一定的認識,再返回頭來看這部分內容,便很好理解了。
控制器是MVC模式中的三個核心元素之一,主要負責相應用戶的輸入、對輸入數據的處理以及對相關視圖輸出數據的提供。
控制器基礎
首先,我們看一下上一篇《ASP.NET MVC5(一):ASP.NET MVC概覽》創建的新項目MyFirstMvcProject中默認包含的幾個控制器類:
- HomeController:負責網站根目錄下的Home Page、About Page和Contact Page。
- AccountController:響應與賬戶相關的請求,如登錄和注冊。
- ManageController:響應啟用外部服務認證的相關頁面。
展開MyFirstMvcProject項目的Controller目錄,打開HomeController.cs文件,代碼如下。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MyFirstMvcProject.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
這是一個非常簡單的控制器類,它繼承自Controller基類。Index方法負責決定當瀏覽網站首頁時觸發的事件。
控制器的創建
創建一個新的控制器,步驟如下:
- 右擊Solution Eplorer下的Controllers文件夾,選擇Add|Controller選項。
- 選擇MVC 5 Controller - Empty模板,點擊Add按鈕。
- 將控制器命名為FirstController,點擊Add按鈕。
此時,已經創建了一個名為FirstController的控制器,並且控制器類中已經包含了一個Index方法,我們將這個方法稍作修改(將返回類型修改為String類型,然后將返回值修改為This is my First controller !),代碼如下:
public string Index()
{
return "This is my first controller !";
}
啟動項目,修改Url瀏覽到/First/Index,然后返回響應的字符串,如下圖。
前面的例子返回的是字符串常量,下一步,則是通過響應Url傳進來的參數動態的執行操作。修改Index方法如下:
public string Index(string message)
{
return "This is my first controller ! The message is '" + message + "'";
}
啟動項目,修改Url瀏覽到/First/Index?message=I'm message。
由上述演示可以得出以下結論:
- 判斷一個類是否為控制器的方式,就是看這個類是否繼承自System.Web.Mvc.Controller。
- 不需要做額外的設置,直接修改Url瀏覽到First/Index就可以執行First類中的Index方法,這就是操作中的路由。
視圖
視圖簡介
視圖的作用是向用戶提供可視的用戶界面。當控制器針對被請求的Url執行完合適的邏輯后,就將要顯示的信息(模型)委托給視圖。這一過程分兩部操作,檢查模型對象以及將內容轉換為HTML格式。
視圖約定
視圖約定:在與控制器同名的View文件夾下,每一個操作方法都有一個同名的視圖文件與其對應,這就提供了視圖與操作方法關聯的基礎。
注意:以上約定可以重寫,如果希望操作方法渲染不同的視圖,只需向其提供一個不同的視圖名稱,在使用這種語法時,必須提供視圖文件的擴展名。我們在FirstController中添加Contact方法,使其渲染Home下的Contact頁面。
public ActionResult Contact()
{
return View("~/Views/Home/Contact.cshtml");
}
啟動示例,將Url定位到/First/Contact,可以看出渲染的頁面是Home下的Contact視圖。
###視圖創建 創建視圖最簡單的方法就是找到Views文件夾下與Action對應的位置,點擊右鍵Add|View。不過Visual Studio中提供了一種更為方便的創建視圖的方法,只需在Controller對應的方法上右擊,選擇AddView項,即可創建視圖。
###強類型視圖 在MVC中,控制器與視圖之間的數據傳遞可以使用ViewBag、ViewData,示例如下: 假設現在需要編寫一個視圖,顯示所有的用戶信息,首先,在Models文件夾下創建一個UserInfo類。
namespace MyFirstMvcProject.Models
{
public class UserInfo
{
public int UserId { get; set; }
public string UserName { get; set; }
public string RegisterDate { get; set; }
}
}
在FirstController類下添加List方法:
public ActionResult List()
{
var userInfo = new List<UserInfo>()
{
new UserInfo {UserId = 1,UserName = "Aaron",RegisterDate = "2015-08-08" },
new UserInfo {UserId = 2,UserName = "Bob",RegisterDate = "2010-03-23" },
new UserInfo {UserId = 3,UserName = "Hunter",RegisterDate = "2014-09-12" }
};
ViewBag.UserInfo = userInfo;
return View();
}
創建視圖,在視圖中迭代並顯示用戶信息:
<table class="table">
@foreach (var item in ViewBag.UserInfo) {
<tr>
<td>
@item.UserId
</td>
<td>
@item.UserName
</td>
<td>
@item.RegisterDate
</td>
</tr>
}
</table>
運行程序,效果圖如下:
ViewBag的不足:向視圖傳遞少量數據時,使用ViewBag很容易完成,當需要傳遞實際的對象時,就變的不太方便,主要體現在無法使用智能感知、獲得強類型和編譯時檢查。此時,就可以使用強類型視圖。
修改FirstController下的List方法:
public ActionResult List()
{
var userInfo = new List<UserInfo>()
{
new UserInfo {UserId = 1,UserName = "Aaron",RegisterDate = "2015-08-08" },
new UserInfo {UserId = 2,UserName = "Bob",RegisterDate = "2010-03-23" },
new UserInfo {UserId = 3,UserName = "Hunter",RegisterDate = "2014-09-12" }
};
return View(userInfo);
}
重新編譯該項目后,修改視圖時發現已經可以獲得智能感知。
運行程序,效果圖如下:
補充:ViewBag和ViewData的區別
- ViewBag是Dynamic類型的對象,ViewData是字典類型的對象。
- 只有當關鍵字是一個有效的C#標識符時,才可以使用ViewBag,例如:ViewData["User Name"],關鍵字中包含空格,此時若使用ViewBag無法通過編譯。
- ViewBag動態值不能通過參數傳遞給擴展方法。例如: @Html.TextBox("name",ViewBag.UserName)無法通過編譯,此時可以使用ViewData["UserName"],或 @Html.TextBox("name",(String)ViewBag.UserName)。
###Razor視圖引擎 Razor的概念:Razor是一個干凈的、輕量級的、簡單的視圖引擎,不包含原有的Web Forms視圖引擎的語法累贅,最大限度地減少語法和額外的字符。Razor通過理解標記的結構來實現代碼和標記之間盡可能順暢地轉換。 Razor中的核心轉換字符是 @符號,使用這個字符用來做標記-代碼的轉換字符。如上一節中的示例,Razor視圖引擎可以根據 @符號自動識別<td>標記以及item.UserId代碼。
<td>
@item.UserId
</td>
Razor視圖引擎除了支持代碼表達式外,還支持代碼塊。
@foreach (var item in Model) {
<tr>
<td>
@item.UserId
</td>
<td>
@item.UserName
</td>
<td>
@item.RegisterDate
</td>
</tr>
}
###布局 布局的使用有助於使應用程序中的多個視圖保持一致的外觀,作用與Web Forms中的模板頁相同。下面是ASP.NET MVC5新建項目時默認布局的部分代碼(位於Views/Shared/_Layout.cshtml)。
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - My ASP.NET Application</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
視圖中 @RenderBody相當於一個占位符,用來標記使用這個布局的視圖將渲染它們的位置。
當創建一個默認的ASP.NET MVC項目時,Views目錄下會自動添加一個_ViewStart.cshtml文件,這個文件會先於任何視圖運行,如果一組視圖擁有共同的設置,我們就可以在_ViewStart.cshtml中進行統一設置。
模型
本節,我們將通過一個簡單的示例來講述如何使用EntityFramework來操作模型。
為產品Product建模
在Model文件夾中添加一個新的Product類,右擊Models文件夾,選擇Add|Class,將新類命名為Product,並添加響應的屬性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyFirstMvcProject.Models
{
public class Product
{
//產品編號
public int Id { get; set; }
//產品名稱
public string ProductName { get; set; }
//產品描述
public string Description { get; set; }
//產品價格
public decimal Price { get; set; }
}
}
為產品管理器構造基架
ASP.NET MVC5包含各種基架模板,不同的模板的代碼生成量不同。首先,介紹一下ASP.NET MVC5中的一些常用的基架模板:
- MVC 5 Controller - Empty:
向Controller文件夾添加一個僅帶有Index操作的Controller類,且在其內部出了返回一個默認的ViewResult實例的代碼之外,沒有其他任何代碼。 - MVC 5 Controller with read/write Actions:
向項目中添加一個帶有Index、Details、Create、Edit和Delete操作的控制器。 - MVC 5 Controller with Views,Using Entity Framework:
不僅生成帶有整套Index、Details、Create、Edit和Delete操作的控制器,而且還生成了與數據庫交互的代碼。
使用基架為產品Product創建控制器,步驟如下:
- 右鍵Controller文件夾,選擇Add|Controller。
- 在彈出的Add Scaffold選擇MVC 5 Controller with Views,Using Entity Framework,點擊Add按鈕。
- 在Add Controller對話框中,修改控制器名稱為ProductsController。
- 單擊Data Context class右側的+按鈕,將數據庫上下文名稱修改為ProductStoreDB,單擊Add按鈕。
- 確認完成后回到Add Controller對話框,點擊Add為Product類添加ProductsController及相關視圖。
使用基架為模型創建控制器時,注意添加模型類后要重新編譯該項目,否則在創建時Visual Studio可能會拋出異常
待創建完成后,修改Web.Config文件中的數據庫連接字符串,使其連接到希望連接的數據庫(本例中連接到本機的SQL Server數據庫)
運行程序,將Url瀏覽到/Products/Index,因為此時還沒有新增過產品,所以列表中沒有數據。
點擊Create New鏈接,在添加頁面依次輸入ProductName、Description、Price,點擊Create,返回/Products/Index頁面,數據添加成功。
數據庫初始化與播種
保證數據庫與模型變化同步最簡單的辦法就是允許實體框架重新創建一個現有的數據庫。可以修改Global.asax文件,調用EF的Database類(位於命名空間System.Data.Entity)中的靜態方法SetInitializer,告知EF在應用程序啟動時重新創建數據庫或僅當檢測到模型變化時重建數據庫(DropCreateDatabaseAlways和DropCreateDatabaseIfModelChanges)。
protected void Application_Start()
{
//啟動應用程序時重新創建數據庫
Database.SetInitializer(new DropCreateDatabaseAlways<ProductStoreDB>());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
在實際開發項目時,我們總希望數據庫中能帶有一些數據,以便開發時用來測試。下面介紹播種數據庫(seeding the database)來實現這種情況,在Models文件夾下創建一個DropCreateDatabaseAlways類的派生類,重寫其中的Seed方法,為應用程序創建一些初始的數據。
public class ProductStoreDbInitializer : DropCreateDatabaseAlways<ProductStoreDB>
{
protected override void Seed(ProductStoreDB context)
{
context.Products.Add(new Product
{
ProductName = "Apple Pencil",
Description = "iPad Pro - Apple Pencil - Apple",
Price = 99m
});
base.Seed(context);
}
}
在應用程序啟動代碼部分注冊這個初始化器,如下所示:
protected void Application_Start()
{
//啟動應用程序時重新創建數據庫,並播種數據庫
Database.SetInitializer(new ProductStoreDbInitializer());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
重啟並運行應用程序,在瀏覽器中導航到/Products/Index,此時不必輸入就有數據可用。