[翻譯]《ASP.NET MVC 3 高級編程》第四章:模型(Professional ASP.NET MVC 3 --- Chapter 4: Models)


博主注:其實我並不是什么翻譯作者,甚至現在連技術人員都算不上,只是喜歡技術,然后想分享一些自己看過的不錯的內容,如果侵害了某些人的利益,請提出;如果需要轉載,請注明出處;如果有錯誤,歡迎指出,歡迎交流。】

關注焦點

  • 模型化音樂商店
  • 基架是什么意思
  • 怎么編輯專輯
  • 關於構建模型的所有事情

 

在軟件開發中“模型”這個詞有數百個不同的含義。可以是成熟度模型、設計模型、測試模型或者是進度模型。罕有某次會議我們沒有討論關於某種模型或者其他模型相關的內容。你也許還記得在第3章討論過的內容,在MVC設計模式的背景下,你依然可以討論面向業務的模型,這也同樣是針對視圖的具體模型對象。

 

這章節會講關於發送信息到數據庫的對象模型,高性能計算模型以及視圖傳遞模型。換句話說,這些模型可能更加側重於代表應用程序域中代表對象的保存、添加、更新以及刪除。

 

ASP.NET MVC3提供了一些工具和功能可以通過定義模型對象來構建應用程序的基本功能。你可以坐下思考你要解決的問題(比如讓顧客如何購買音樂),並編寫簡單的C#類,如相冊、購物車以及用戶等主要對象。然后,當你准備好后,你可以使用工具構建控制器並為每個模型創建IndexCreateEditDelete的標准視圖。這個構造工作是由基架來完成的,但是在討論基架之前,需要做一些關於模型的工作。

 

模型化音樂商店

想象你需要從頭使用ASP.NET MVC來構建音樂商店。開始就像所有偉大的程序一樣,首先要在Visual Studio中點擊“文件”→“新建工程”。在輸入工程名稱之后點擊“新建”,Visual Studio就會展示一個如圖4-1的對話框,這時可以在Visual Studio中選擇名為的“Internet Application”項目模版。

Machine generated alternative text: New ASRNET MVC 3 ProjectProject TemplateDescription:A default ASP.NET MVC 3 project with anaccount controller that uses formsauthentication.view engine:[RazorRi create a unit test projectTest aroject name:MvcMusicStore.TestsTest framework:HRI Use HTML5 semantic markupy Additional Infoeled a template:aEmpty [Internet Intranetication Applicationf Visual Studio Unit TestL OK ] [ cancel ]

圖例 4-1

 

這個Internet應用程序工程模版會創建站點所需的所有基礎功能(如圖4-2):一個共享布局視圖、一個鏈接到客戶登錄頁面的首頁、一個初始的樣式表以及一個相對比較空的“Models”文件夾。在Models文件夾中有一個AccountModels.cs的文件,所有與賬戶管理相關的模型類都在這個文件中(注冊視圖表單模型類,登錄模型類以及修改密碼的模型類)。

 

Machine generated alternative text: Solut靜n Explorer e fl xSolution MvcMusicStore (2 projects)4 MvcMusicstorePropertiesReferencesLZj App_DataContent?  Controllers4 ModelsAccountModels.csScriptsViewsGlobal.asaxpackages.configWebconfigMvcMusicStore.Tests

圖例4-2

 

為什么Models文件夾這么空呢?因為項目模版並不知道你當前要解決什么類型的問題。

 

可能現在你也同樣不知道你當前遇到的是什么樣的問題。你可能需要與客戶或者管理人員進行交流,進一步確定問題的原型進行設計,或者確定測試模型以驅動開發。ASP.NET MVC並不會規定工作的工程或者方法。

 

最終,我們假設你決定建設一個音樂商店。首先,需要有列表展示、創建、編輯和刪除音樂專輯的信息的功能。專輯模型將會有以下的屬性:

 

public class Album
{
public virtual int AlbumId { get; set; }
public virtual int GenreId { get; set; }
public virtual int ArtistId { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
public virtual string AlbumArtUrl { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
}

 

專輯模型類主要是模擬音樂專輯的屬性,如標題和價格。同樣,每張專輯也會有一個單一的藝術家:

 

public class Artist
{
  public virtual int ArtistId { get; set; }
  public virtual string Name { get; set; }
}

 

你可能會注意到每個Album類都有兩個關於藝術家的屬性:Artist屬性和ArtistId屬性。我們將Artist屬性稱為“導航屬性”,你可以使用“.”點運算符查看專輯藝術家的屬性(例如:favoriteAlbum.Artist)。

 

我們將ArtistId屬性成為“外鍵屬性”,如果你了解一點關於數據庫工作原理的知識,你應該知道專輯和藝術家將會分別在兩個不同的表中保存。每個藝術家可能會和多個專輯記錄保持關聯,而外鍵的作用就是將藝術家表中的記錄和專輯表中的記錄保持關聯,你可能會需要在專輯的模型類中關聯藝術家模型類的外鍵。

 

模型關系

我敢肯定有些讀者會不喜歡在模型類中使用外鍵屬性的想法,因為外鍵是關系型數據庫中關聯的方式。外鍵在模型類中並不是必須的,所以可以去除掉。在本章中,你將會使用外鍵屬性,它會為將要使用的工具提供很多方便的功能。

 

專輯也會有相關的流派信息,每個流派模型類中也會有一個相關專輯的列表:

 

public class Genre
{
public virtual int GenreId { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual List<Album> Albums { get; set; }
}

 

你可能還注意到,現在每個屬性是虛擬的。我將在本章后面的部分進行解釋。現在,我將以這三個模型類作為起點,講解你需要了解的控制器的基架,一些視圖甚至還要創建一個數據庫。

 

基架生成存儲管理器

 

接下來需要創建一個存儲管理器,以便你可以編輯專輯的信息。首先,在解決方案中,右鍵單擊Controllers文件夾,選擇“添加控制器”,在出現的對話框中(如圖4-3所示),設置控制器名稱,並選擇基架選項。並依圖選擇基架模版中的模型類和數據上下文。

 

Machine generated alternative text: Add ControllerController name:StoreManagerControllerScaffolding optionslemplate: _________[Controller with read/write actions and views, using Entity FrameworkModel class:Album {MvcMusicStore.Models)2ata context class:Views:Razor (CSHTML) Advanced Opt阰ns.L Add J j Cancel J

圖例 4-3

 

基架是什么?

 

ASP.NET MVC 3中基架依照模版為應用程序生成創建、讀取、更新和刪除(CRUD)功能的代碼。基架模版會根據定義的模型類型(如剛才創建的Album類),生成相應的控制器以及控制器相關的視圖。基架知道如何命名控制器,如何命名視圖以及每個組件中如果那些代碼,也知道生成后的那些文件在應用程序項目中如何放置。

 

基架的功能

幾乎所有MVC框架都是一樣的,如果你不喜歡默認基架提供的功能,你可以自定義或更換生成代碼的策略以滿足你的需求。也可以通過NuGet找到其他基架模版(搜索“scaffolding”即可)。NuGet資源庫可以提供特定的設計模式或應用技術的代碼生成功能。如果你真的不喜歡基架的做法,也可以隨時動手一切從頭開始。構建應用程序並不需要必須使用基架,只是利用它在一定程序上可以節省工作時間而已。

 

不要指望基架可以建立一個完整的應用程序。相反,基架只是可以讓你從枯燥的工作以及手工編碼中解放出來,讓你可以專心的做一些編碼以外的工作。你也可以通過調整和邊際基架,使應用程序按照你的意願輸出代碼。基架只在你要求的時候才運行,所以不必擔心它輸出的代碼會覆蓋你所做的其他工作。

 

MVC 3中有三種基架模版,你可以通過選擇不同的基架模版生成代碼。

 

空控制器

空控制器會根據你指定的名稱在Controllers文件夾中生成Controller的派生類。在控制器中只會生成一個Index方法以及默認的ViewResult返回值。這個模版不會生成任何視圖。

 

控制器以及空讀/寫動作

這個模版會添加一個控制器到項目中,並生成IndexDetailsCreateEditDelete動作。這些動作都空方法,這些方法沒有有效的執行內容,需要你自己去添加代碼並為每個動作創建視圖。

 

控制器以及讀/寫動作和視圖,使用Entity Framework

這將是要選擇的模版。使用這個模版不僅會生成整套的控制器和IndexDetailsCreateEditDelete動作,還會生成所需的視圖和從數據庫中檢索信息的代碼。

 

為了可以生成正確的代碼,需要選擇一個模型類(如圖4-3所選擇的Album類)。基架會檢查所選擇的模型的所有的屬性和使用信息,並建立控制器、視圖和數據訪問代碼。

 

要生成數據訪問代碼,還需要提供一個數據上下文類,可以為基架指定一個已存在的數據上下文類,或者提供一個對象名稱,基架創建一個新的數據上下文。至於什么是數據上下文,接下來,我會做一個關於實體框架的簡要介紹。

 

基架和實體框架

在一個新的ASP.NET MVC 3項目中會自動添加一個Entity Framework 4.1(這個並不是最新的版本,而是.NET 4.0中自帶的版本)的引用。EF是一個懂得如何將.NET對象存儲到關系型數據庫中並可以使用LINQ查詢檢索相應的對象數據的對象關系映射框架。

 

靈活的數據選項

ASP.NET MVC 應用程序並不強制要求或依賴與實體框架。事實上你可以使用任意一種數據庫、關系或方式的框架,也可以使用任何數據訪問技術或數據源構建應用程序。即使你想使用逗號分割的文本文件或者WS-*協議的Web   Services都可以。但是在本章中會使用EF 4.1,其中涉及到的很多方法都適用於任何數據源。

 

EF 4.1支持代碼優先的開發方式。代碼優先就是你可以在SQL Server中存儲和檢索數據而不需要創建數據庫大綱或者是打開Visual Studio設計器。相反,只需要純粹的寫C#代碼和視圖關系,怎么去存儲在哪存儲這些類。

 

還記得剛才定義的模型對象中所有成員都是虛擬的嗎?虛擬成員並不是必需的,但是它就像在C#類中啟用了EF鈎子的高效率修改跟蹤機制一樣。當一個模型更改了屬性值時,實體框架就會知道它做出的修改變化並更新相應的SQL UPDATE數據庫語句。

 

 

應該先做什么——代碼還是數據庫?

如果你已經對實體框架非常熟悉,你可以使用模型優先或大綱優先的方式去開發,MVC的基架也同樣支持這樣的操作。實體框架團隊設計使用代碼優先的方法,為了可以將開發人員從重復的代碼工作和數據庫工作中解放出來,創造一個更開放的環境。

 

 

代碼優先守則

EF,就像ASP.NET MVC一樣,使用如下的規則可以讓生活更輕松一些。例如,如果你想將Album對象存儲到數據庫中,EF假設你你想存儲的對象的表名為“Albums”。如果對象有一個屬性名為“ID”,EF就會假定這個屬性值為SQL Server數據庫中的自增長主鍵。

 

EF也有外鍵關系、數據庫命名等其他設定。這些約定可以取代之前所有對象關系映射框架所提供的映射和配置操作。使用代碼優先的開發方式從零開發應用程序會變的非常快速,如果你需要與現有的數據庫進行映射,你只需要提供映射元數據(也可以使用實體框架的大綱優先的開發方式)。如果您想了解更多關於實體框架的內容,可以訪問MSDN的數據開發中心(http://msdn.microsoft.com/en-us/data/aa937723)。

 

數據上下文類 DbContext

當你選擇使用代碼優先的方式,DbContext類的派生類就是數據庫的網關。派生類中會擁有一個或多個DbSet<T>類型的屬性,其中T代表強類型對象。例如,下面這個類就是用來存儲和檢索專輯和藝術家信息的:

 

public class MusicStoreDB : DbContext
{
public DbSet<Album> Albums { get; set; }
public DbSet<Artist> Artists { get; set; }
}

 

使用上面定義的數據上下文,使用LINQ查詢並按照專輯標題字母順序返回所有專輯信息,代碼如下:

 

var db = new MusicStoreDB();
var allAlbums = from album in db.Albums
orderby album.Title ascending
select album;

 

現在,你了解了一些關於內置的基架模版技術的知識,接下來讓嘗試一下看基架會生成什么樣的代碼。

 

運行基架模版

回到“添加控制器”對話框中(參見圖例4-3),在選擇數據上下文的下拉框中,選擇新的數據上下文。如圖例4-4所示新的數據上下文對話框,需要您輸入使用的類名稱和數據庫名稱(要包括類的命名空間)。

 

Machine generated alternative text: New Data ContextNew data context type:MvcMusicStore.Models.MusicStoreDBlOK j [ Cancel ]

圖例 4-4

 

將上下文命名為MusicStoreDB,點擊“確認”,然后在新建控制器對話框中點擊“添加”(如圖例4-5),完成創建。基架化一個Album類的StoreManagerController控制器和相應的視圖。

 

Machine generated alternative text: Add ControllerController name:StoreManagerControllerScaffolding optionslemplate:[Controller with read/write actions and views, using Entity FrameworkModel class:Album (MvcMusicStore.Models)Qata context class:MvcMusicStore.Models.MusicStoreDBViews:[Razor (CSHTML) Advanced Opt阰ns.[ Add ] [ Cancel J

圖例 4-5

 

點擊“添加”按鈕之后,基架會將生成的動作文件添加到本地項目中相應的文件夾中。先讓我們來分析一下當前生成的文件再繼續進行接下來的工作。

 

數據上下文

基架在工程中的“Models”文件夾中添加了一個“MusicStoreDB.cs”文件。文件中的類是從實體框架中的DbContext類派生而來,並提供了訪問數據庫中的專輯、流派和藝術家的信息。即使是你只為基架指定了Ablum模型類,基架也會在上下文中包含其它與Ablum類相關的模型類。

 

public class MusicStoreDB : DbContext
{
public DbSet<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Artist> Artists { get; set; }
}

 

要訪問數據庫,你需要實例化數據上下文類。你可能需要知道上下文類將使用什么數據庫,這個問題我會在第一次運行應用程序時解答。

 

StoreManagerController 控制器

在選擇模版之后,基架會在應用程序的Controllers文件夾中生成StoreManagerController控制器類。這個控制器將包含檢索和編輯專輯信息的代碼。先看幾行代碼:

public class StoreManagerController : Controller
{
private MusicStoreDB db = new MusicStoreDB();

//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}

// more later …

 

在這段代碼片段中,看到控制器中包含了一個私有的MusicStoreDB類型的成員。因為每個控制器的動作都需要訪問數據庫,基架會在這里初始化一個數據上下文的實例。在Index動作中,你可以看到代碼中,默認視圖模型視圖通過上下文對象從數據庫中加載所有專輯的列表。

 

加載關聯對象

Index動作中調用Include方法告訴實體框架要加載專輯的流派和藝術家信息。在查詢專輯時上下文會根據默認策略嘗試加載所有數據,實體框架還有另外一種選擇(默認)延遲加載策略,EFLINQ查詢時只加載主要對象(Album)而不會加載流派和藝術家信息:

 

var albums = db.Albums;

 

延遲加載會根據需要只加載主要對象相關的數據,例如專輯的流派或藝術家信息則是在觸發訪問時,EF會再次發送額外的查詢到數據庫中檢索數據。不過,如果使用延遲加載的策略,實體框架會為每個專輯發送一個額外的查詢。對於100個專輯來說,如果選用延遲加載策略,總共需要101次查詢。我們剛才描述的情景被成為N-1問題(因為框架執行了101次查詢,帶回來了100個填充對象)。這是每個對象關系映射框架所共同面對的問題。延遲加載是非常方便的,但是代價也是非常昂貴的。

 

你可以進行一些必要的優化,以減少構建完整模型時的所產生的查詢次數。要了解更多關於延遲加載的信息,請參閱MSDN上關於“Loading Related   Objects”(http://msdn.microsoft.com/library/bb896272.aspx)。

 

基架也會生成創建、編輯、刪除和顯示專輯詳情的動作。在本章后面的部分會展示編輯動作的功能。

 

視圖

基架執行完畢之后,你會在“Views/StoreManager”文件夾中找到生成的所有視圖文件。這些視圖將提供展示、編輯和刪除專輯的用戶界面。可以在圖例4-6中看這些文件的列表。

 

Machine generated alternative text: ExplorerAji  ViewsAccount>  HomeJJ SharedStoreManagerC Create.cshtmlDelete.cshtmlDetails.cshtmlEdit.cshtmlIndex.cshtmlC _ViewStart.cshtmlWeb.configGlobal.asaxpackages,configWeb.configMvcMusicStore.Tests

圖例4-6

 

Index視圖中的代碼是一個用來顯示所有音樂專輯的表格。Index視圖從Model對象中枚舉的所有Ablum對象都是由之前Index動作中所提供的。在視圖中通過使用foreach循環語句創建HTML表格中的行:

  

@model IEnumerable<MvcMusicStore.Models.Album>

@{
ViewBag.Title = “Index”;
}

<h2>Index</h2>

<p>@Html.ActionLink(“Create New”, “Create”)</p>
<table>
<tr>
<th>Genre</th>
<th>Artist</th>
<th>Title</th>
<th>Price</th>
<th>AlbumArtUrl</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Genre.Name)</td>
<td>@Html.DisplayFor(modelItem => item.Artist.Name)</td>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
<td>@Html.DisplayFor(modelItem => item.Price)</td>
<td>@Html.DisplayFor(modelItem => item.AlbumArtUrl)</td>
<td>
@Html.ActionLink(“Edit”, “Edit”, new { id=item.AlbumId }) |
@Html.ActionLink(“Details”, “Details”, new { id=item.AlbumId }) |
@Html.ActionLink(“Delete”, “Delete”, new { id=item.AlbumId })
</td>
</tr>
}
</table>

 

請注意基架是如何“處理”所有自定義成員。進一步說,就是,視圖的表格中不會顯示任何外鍵屬性值(就是跳過了一個自定義屬性),但是顯示了流派名稱以及相關的藝術家的名字。視圖使用的HTML的輔助類輸出了所有的模型屬性。

 

遞歸在表格的每個行中都生成一個編輯、刪除和顯示詳情的鏈接。如前所說,基架生成的代碼只是程序的一個起點,你可以根據自己的意願對代碼進行添加、刪除和修改。但是在更改之前我們需要先看看當前的視圖是什么樣子的。

 

執行基架代碼

在運行應用程序之前,讓我們看一個亟待解決的問題——MusicStoreDB要使用什么數據庫?你還沒有創建或為應用程序指定一個數據庫連接。

 

通過實體框架創建數據庫

使用代碼優先的方法,EF使用會由於配置使用模型約定。如果你不為模型指定映射數據庫中的表和列。在運行時為程序指定一個數據庫鏈接,EF將根據模型約定創建數據庫。

 

配置數據庫鏈接

只需要在web.config文件中添加數據庫鏈接字符串,連接字符串的名稱必須與數據上下文類的名稱保持一致。參考我們之前已經完成的數據上下文的代碼,我們需要進行如下配置:

<connectionStrings>
<add name=”MusicStoreDB”
connectionString=”data source=.\SQLEXPRESS;
Integrated Security=SSPI;
initial catalog=MusicStore”
providerName=”System.Data.SqlClient” />
</connectionStrings>

 

 

如果沒有配置數據庫鏈接,EF將嘗試連接本地的SQL Server Express實例,並查找與DbContext派生類名稱相同的數據庫。如果連接到數據庫服務器,但是沒有找到數據庫,EF將會創建數據庫。如果你在基架執行完成后運行應用程序,你將會在/StoreManager的文件夾中找到由實體框架創建的名為“MvcMusicStore.Models”的數據庫。MusicStoreDB在本地計算機的SQL Express實例中。你可以通過以下圖例4-7看到完整的數據庫關系。

 

Machine generated alternative text: EdmMetadataIdModelHashAlbumsAlbumldArtistldGenreldTitle GenresPrice Cx 0  GenreldAlbumArtUrl NameDescriptionArtistsArtistidName

圖例4-7

 

實體框架將會自動創建表來存儲專輯、藝術家和流派信息。該框架將根據模型的屬性名稱和類型來確定表中類的名稱和數據類型。注意,框架還會根據模型中的屬性推導出表的主鍵列和表之間的外鍵關系。EF會通過EdmMetadata數據庫中的表確保模型類與數據庫架構同步(通過計算模型類的哈希值)。不需要為更改(例如:添加一個屬性、刪除屬性或添加一個類)模型而擔心,EF會基於新的模型重新創建基礎數據庫,或者拋出異常。EF也不會在未經許可的情況下重新創建數據庫,而是需要你提供一個數據庫初始化方法。

 

EDMMETADATA

EF並不需要EdmMetadata表在你的數據庫中。這些表只是為了檢測模型類的變化。如果你明白在干什么,你可以隨意的刪除EF中的EdmMetadata表。一旦你刪除EdmMetadata表,你(或者你的數據庫管理員)將負責手工同步數據庫結構和模型類結構。你可以手工調節保持模型和數據庫之間的映射同步。映射的入門知識請查看MSDN相關內容:http://msdn.microsoft.com/library/gg696169(VS.103).aspx

 

使用數據庫初始化

模型變化時,保持數據庫同步一個簡單的方法就是允許實體框架重新創建一個現有的數據庫。每次程序啟動時,只要實體框架檢測到模型中有變化的內容,就調用Database對象的SetInitializer靜態方法(包含在System.Data.Entity命名空間)重新創建數據庫。

 

當你調用SetInitializer方法時,需要提供一個IDatabasesetInitializer的實例化對象,框架提供了兩個可實例化類型:DropCreateDatabaseAlwaysDropCreateDatabaseIfModelChanges。在實例化這兩個類型對象時都需要指定一個泛型參數,這個參數必須是DbContext的派生類。

 

就像下面這個例子,如果程序每次啟動的時候都重新創建音樂商店的數據庫,就需要在global.asax.cs中,可以在應用程序啟動的時候設置數據庫初始化:

 

protected void Application_Start()
{
Database.SetInitializer(new DropCreateDatabaseAlways<MusicStoreDB>());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

 

你可能想知道為什么每次應用程序啟動的時候都要重新創建一個數據庫?即使在模型改變時,你是否想保留里面的數據呢?這些都是有意義的問題,在代碼優先中的一些特殊方法(如數據庫的初始化),只是在應用程序的開發早期為方便迭代和快速適應變化而采用的。一旦應用程序完成部署,數據庫中都存儲的客戶的實際數據,就不能每次因為模型的變化,而重新創建數據庫。

 

當然,在項目的初級階段,你也依然可以保留數據庫中的數據,或者至少可以有一些初始的記錄,填充到新的數據庫中。

 

初始化數據庫數據

在應用程序每次啟動的時候都會重新創建一次MVC音樂商店的數據庫。不過,我們希望在重新創建數據庫之后,就會有一些流派、藝術家甚至是專輯的內容,可以讓我們及時開始工作。

 

在這種情況下,需要從DropCreateDatabaseAlways類派生一個類,並重寫Seed方法。在Seed方法中,可以在創建應用程序的時候初始化一些數據,代碼如下所示:

 

public class MusicStoreDbInitializer
: DropCreateDatabaseAlways<MusicStoreDB>
{
protected override void Seed(MusicStoreDB context)
{
context.Artists.Add(new Artist {Name = “Al Di Meola”});
context.Genres.Add(new Genre { Name = “Jazz” });
context.Albums.Add(new Album
{
Artist = new Artist { Name=”Rush” },
Genre = new Genre { Name=”Rock” },
Price = 9.99m,
Title = “Caravan”
});
base.Seed(context);
}
}

 

調用基類中的Seed方法,將新對象保存到新建數據庫中。在音樂商店數據庫實例中會有兩個流派(JazzRock),兩位藝術家(Al Di MeolaRush)和一個專輯。為新數據庫初始化工作,您需要更改應用程序的啟動代碼,以注冊初始化工作:

 

protected void Application_Start()
{
Database.SetInitializer(new MusicStoreDbInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

 

如果您現在重新運行應用程序,導航到/StoreManager地址,你將會看到如圖4-8所示內容:

 

Machine generated alternative text:

圖例4-8

 

瞧!應用程序運行的實景功能!還有實景數據!

 

雖然似乎提出了很多的工作,但是本章到目前為止,幾乎所有的代碼都是由實體框架生成的。一旦你知道了基架是如何工作的,所需的工作就非常簡單了,大概只需要三個步驟:

 

  1. 實現你需要的模型類;
  1. 由基架生成你的控制器和視圖;
  1. 選擇初始化數據庫的方式並初始化。

 

記住,基架只是為應用程序開發提供了一個起點。你可以自由調整和修改代碼。例如,如果你不喜歡每張專輯右側的鏈接(編輯、詳情、刪除)。你可以從視圖中刪除這些鏈接。在接下來的編輯場景中,你將會看到如果編輯ASP.NET MVC模型。

 

編輯專輯

基架處理的場景之一就是編輯專輯,如圖4-8,當用戶在Index視圖中點擊“編輯”鏈接。“編輯”鏈接就會發送一個HTTP Get請求到Web服務器訪問“/StoreManager/Edit/8”(其中8為某個特定相冊的ID)的地址。你可以認為這就是請求“我要編輯8號專輯”。

 

構建相冊編輯頁面

根據默認的MVC路由規則,HTTPGET訪問的/StoreManager/Edit/8路徑是StoreManager控制器中的Edit動作:

//
// GET: /StoreManager/Edit/8
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, “GenreId”, “Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
Name”, album.ArtistId);
return View(album);
}

 

Edit動作的責任就是構建一個模型去編輯8號專輯。它從MusicStoreDB中檢索到所需的專輯,然后將獲取的內容作為視圖的模型類。但是賦值的兩個ViewBag變量有什么意義呢?如圖4-9看到的專輯編輯頁面,這兩行代碼讓你看到的頁面更加有意義:

Machine generated alternative text: EditAlbumArtistRushGenreRock[RockJazzPrice9.99ALbumAitUrl[Save j

圖例4-9

 

當用戶編輯專輯信息時,我們並不想用戶自己輸入流派和藝術家信息,而是希望用戶可以從數據庫中選擇已存在的流派和藝術家條目。基架非常智能,它可以通過我們定義的模型了解專輯、藝術家和流派之間的關系。

 

基架不會生成輸入信息的文本框,而是在編輯視圖中生成下拉框選擇現有的流派信息。下面的代碼就是存儲管理的編輯視圖中生成流派下拉框的代碼(如圖4-9所示,點擊流派會顯示兩條可選信息):

 

<div class=”editor-field”>
@Html.DropDownList(“GenreId”, String.Empty)
@Html.ValidationMessageFor(model => model.GenreId)
</div>

 

在下一章我們會詳細介紹關於下拉列表的更多細節,但是現在,我們還是需要從頭來構建圖例中的下拉列表。要構建列表,你需要知道所有可用的列表項。一個專輯模型並不能從數據庫中檢索到所有的流派信息,一個專輯只有一個流派與之對應。所有,就會有如下兩行代碼,檢索所有的流派信息和藝術家信息都存儲在ViewBag變量中,在編輯操作時將其綁定到下拉列表中。

 

ViewBag.GenreId = new SelectList(db.Genres, “GenreId”, “Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”, “Name”, album.ArtistId);

 

代碼通過SelectList類填充數據構建一個下拉列表。構造函數的第一個參數是列表中項目;第二個參數是用戶要選擇的字段的標識(一般為主鍵值,例如522);第三個參數列表項中要顯示的文本;最后的參數是默認選中項。

 

模型和視圖模型終極版

是否還記得前一章談過的視圖模型的概念?編輯專輯就是一個非常好的案例,現在使用的模型對象並沒有包含視圖需要的所有信息。比如我們需要的所有流派和藝術家列表。對於這個問題有兩個解決方案。

 

上面基架生成的代碼,展示了第一個種方案——通過ViewBag變量傳遞額外的信息。這種方式合理合法易於實現,只是有些人可能希望通過強類型的模型對象提供所有模型數據。強類型愛好者可能會使用第二種方案,就是專門為編輯視圖建立一個的專輯、流派和藝術家的模型對象,這個模型可能使用如下定義:

 

public class AlbumEditViewModel
{
public Album AlbumToEdit { get; set; }
public SelectList Genres { get; set; }
public SelectList Artists { get; set; }
}

 

Edit視圖將通過Edit動作實例化AlbumEditViewModel類並設置相關屬性值,而不是將值存到ViewBag中,並不是說這就是最優的方法,只是你需要選擇哪種方式更適合你或你的團隊的個性。

 

Edit視圖

 

下面的代碼並不完全是Edit視圖中的內容,但是它也反映了Edit視圖的本質:

@using (Html.BeginForm()) {
@Html.DropDownList(“GenreId”, String.Empty)
@Html.EditorFor(model => model.Title)
@Html.EditorFor(model => model.Price)
<p>
<input type=”submit” value=”Save” />
</p>
}

 

視圖中包含各種不同的用戶輸入信息方式。有些下拉列表(HTML <Select>元素)也有些TextBoxHTML <input type="text" >元素)控件。Edit視圖最終要呈現的內容代碼如下:

 

<form action=”/storemanager/Edit/8” method=”post”>
<select id=”GenreId” name=”GenreId”>
<option value=””></option>
<option selected=”selected” value=”1”>Rock</option>
<option value=”2”>Jazz</option>
</select>
<input class=”text-box single-line” id=”Title” name=”Title”
type=”text” value=”Caravan” />
<input class=”text-box single-line” id=”Price” name=”Price”
type=”text” value=”9.99” />
<p>
<input type=”submit” value=”Save” />
</p>
</form>

 

當用戶點擊頁面的“保存”按鈕時,HTML會發送一個HTTP POST請求到地址“/StoreManager/Edit/8”。瀏覽器會自動根據輸入形式收集並發送用戶輸入的數據(及其相關名稱)。注意!在HTML中的name屬性和選擇控件,name屬性會和Album模型類屬性進行匹配。馬上你就會看到命名的重要性了。

 

Edit視圖的POST請求

接受編輯專輯信息的HTTP POST請求的動作名稱也是“Edit”,與之前的Edit動作不同,這個動作上面有HttpPost的屬性標記:

 

//
// POST: /StoreManager/Edit/8
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(“Index”);
}
ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
“Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
“Name”, album.ArtistId);
return View(album);
}

 

這個動作的作用就是接受所有用戶編輯的專輯模型信息,然后將這些信息存儲到數據庫中。你也許會想知道為什么會去更新一個動作傳入的參數,這個問題我將會在本章的下一節來解答。現在,讓我來看看動作本身到底發生了什么。

 

 

正確編輯步驟

正確的路徑是在你提交編輯時,該模型所有的屬性值都是驗證過並可以保存到數據庫中的對象。動作可以通過ModelState.IsValid屬性來檢查模型對象的有效性。我們會在后面的章節中討論這個屬性,在第6章你也會學到如何添加驗證規則模型。現在,你可以認為ModelState.IsValid是用來檢測用戶輸入專輯數據是否有效的標志屬性。

 

如果這個模型對象通過了驗證,我們可以在Edit動作中執行這行代碼:

  

db.Entry(album).State = EntityState.Modified;

 

這行代碼告訴數據上下文類,這個數據對象的值在數據庫中已存在(這不是一個全新的專輯,而是現有的專輯),框架應該去更新數據庫中已存在的專輯對象的記錄而不是嘗試去創建一個新專輯記錄。下面一行代碼是調用數據上下文的SaveChanges方法,生成一個相應的SQL UPDATE命令來更新新值。

 

出現異常的編輯步驟

如果編輯動作存在無效值的模型對象就需要有異常路徑。在異常的路徑中,在用戶出現異常的時候,控制器可以重新執行創建Edit視圖的動作。例如,用戶在專輯價格中輸入abc。字符串abc並不是有效的十進制數值,產生無效模型對象。動作重新構建下拉列表並重新呈現Edit視圖。如圖例4-10所示:

 

Machine generated alternative text: http:/flocalhost:I 9   X  Edit xEditAlbumArstRushLGenreRockETitleCaravanPriceabc The value 慳bc? is notvalid for Price.AlburnArtUrlI Save]

圖示4-10

 

你可能想知道錯誤信息是如何產生的,同樣,我們將在第6章深入討論模型驗證。現在,你將會明白用Edit動作是如何接收到用戶輸入的Ablum對象新值。過程背后是神奇的模型綁定功能,也會講到ASP.NET MVC的模型綁定的核心功能。

 

模型綁定

想像一下,你可以在Edit動作中實現一個HTTP POST請求,而不知道任何關於ASP.NET MVC功能,這回讓你的生活更容易一些。因為你是一個專業的Web開發人員,你會意識到Edit是如何將值傳回服務器的。如果想獲得這些專輯的新值,你可以選擇直接從Request對象中獲取:

  

[HttpPost]
public ActionResult Edit()
{
var album = new Album();
album.Title = Request.Form[“Title”];
album.Price = Decimal.Parse(Request.Form[“Price”]);
// ... and so on ...
}

 

如你想象,這樣會讓代碼變的相當乏味。而上面的代碼只是展示了兩個屬性的想法,但是你必須使用四個、五個甚至更多。從表單的控件集合中獲取屬性值(要使用名字從傳送表單中獲取值)。而其中包含的值只有字符串類型,所以可能每次都需要進行一次類型轉換。

 

幸運的是,Edit視圖會仔細的將表單輸入項的名稱與Ablum對象的屬性值相對應。如果你還記得前面看到的HTML代碼,標題輸入項名稱為“Title”,價格輸入項名稱為“Price”。你可以自己修改視圖以使用不同的名稱(如FooBar),但是這樣做只會讓動作的代碼變得更加難寫。你一定要記得在視圖中,標題的輸入項名稱為“foo”——這是多么荒謬啊!

 

如果輸入的名稱要匹配模塊屬性的名稱,為什么不能以通用的名稱為基礎來形成命名約定呢?這正是ASP.NET MVC所提供的模型綁定功能。

 

DefaultModelBinder

需要獲得表單提交的值,Edit動作只需要獲得一個Album對象參數:

 

[HttpPost]
public ActionResult Edit(Album album)
{
// ...
}

 

當動作有一個模型參數時,MVC運行時會自動將模型和參數進行綁定。你仍然可以使用不同的模型綁定器在MVC運行時中進行綁定,但是默認依舊是DefaultModelBinder綁定器。在之前的命名規則中,默認模型綁定器可以自動轉換和傳送請求Ablum對象的值(模型綁定,也可以創建一個對象的實例來填充)。

 

換句話說,當模型綁定時,Ablum對象中有一個Title的屬性,它就會在請求的參數中查找一個名為“Title”的參數。請注意,我說的是模型綁定器在“請求中”而不是在“表單集合中”查找。模型綁定器可以在路由數據、查詢字符串、表單集合或用戶自定義值等不同區域搜索綁定值。

 

模型綁定也並不限於HTTP POST請求參數,也可以適用於類似於Album的復合類型。模型綁定可以為動作傳入原始參數,比如HTTP POST請求的Edit動作:

public ActionResult Edit(int id)
{
// ….
}

 

在這種情況下,綁定器搜索名稱為(id)的請求參數。模型綁定器通過路由引擎組件發現並傳送網址/StoreManager/Edit/8中的id參數值。也可以請求/StoreManager/Edit?id=8網址,綁定器同樣會獲取到請求中的查詢字符串中的id參數。

 

這個模型綁定器有點像搜索引擎和搜救犬,運行時告訴模型綁定器,它想尋找id值,綁定起就會在它所有可以查找的范圍內搜索名稱為id的參數。

 

模型綁定的安全性

有時在請求動作時,綁定器積極搜索會帶來意想不到的后果。剛才我們提到默認模型綁定設置會在請求時盡可能大方為的匹配每個屬性的值。偶爾會出現並不是你希望匹配的模型綁定設置,這是你就需要小心“over-posting”攻擊。

 

在第7Jon會詳細介紹關於“over-posting”攻擊的詳情,也會介紹幾種技術可以避免這些問題。現在請記住這一威脅,並務必閱讀第7章。

 

明確的模型綁定

當動作包含一個模型參數時,也可以通過隱含的方式執行模型綁定。在控制器中通過使用UpdateModelTryUpdateModel方法來顯式調用模型綁定,如果該模型不存在或未通過驗證的屬性就會拋出模型綁定錯誤的異常。下面的Edit動作就是使用了UpdateModel方法,而不是動作參數:

 

[HttpPost]
public ActionResult Edit()
{
var album = new Album();
try
{
UpdateModel(album);
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(“Index”);
}
catch
{
ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
“Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
“Name”, album.ArtistId);
return View(album);
}
}

 

TryUpdateModel也具有模型檢測能力,但是不會拋出異常。TryUpdateModel方法會返回一個bool結果,通過這個值來判斷模型綁定是否成功:

 

[HttpPost]
public ActionResult Edit()
{
var album = new Album();
if (TryUpdateModel(album))
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(“Index”);
}
else
{
ViewBag.GenreId = new SelectList(db.Genres, “GenreId”,
“Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, “ArtistId”,
“Name”, album.ArtistId);
return View(album);
}
}

 

模型綁定還會有個附產品——“模型狀態”。每一次綁定器將值傳入模型中,這個對象都會記錄這個狀態,模型綁定成功之后,你可以通過這個對象的屬性來檢測模型狀態:

 

[HttpPost]
public ActionResult Edit()
{
var album = new Album();
TryUpdateModel(album);
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction(”Index”);
}
else
{
ViewBag.GenreId = new SelectList(db.Genres, ”GenreId”,
”Name”, album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, ”ArtistId”,
”Name”, album.ArtistId);
return View(album);
}
}

 

如果模型綁定時發生任何錯誤,模型狀態中都將會包含錯誤屬性、錯誤值以及錯誤消息名稱。在接下來的兩章中,通過模型狀態檢測模型綁定下的HTML輔助類和MVC驗證的工作。

 

小結

在這章中你看到如何基於模型對象構建一個MVC應用程序。你可以使用C#代碼來定義模型,然后根據特定模型通過基架來構建應用程序。實體框架的基架是可擴展和可定制的,你可以挑選使用各種各樣的基架進行工作。

 

然后,現在只是簡單的了解了一下模型對象驅動開發應用程序。在后面的部分,還討論了模型綁定以及模型綁定如果從控制器的動作請求中獲取表單集合以及查詢字符串的值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM