ASP.NET MVC實例:使用Northwind和Entity框架
作為asp.net 3.5 Extensions預(yù)覽版組成部分的微軟asp.net mvc框架發(fā)布了CTP版本。自此,mvc框架就吸引了眾多業(yè)界人士的眼球,并紛紛在博客以及各個開發(fā)者所關(guān)注的網(wǎng)站上進(jìn)行了介紹。
微軟CLR和.NET框架團(tuán)隊的成員Brad Abrams發(fā)布了一個非常優(yōu)秀的實例,為開發(fā)者展示了如何有效地使用微軟mvc框架中的某些最新工具。該實例源于Scott Guthrie的mvc實例,Scott將這些內(nèi)容放在他的博客上,演示了在最初形式下的mvc框架是如何進(jìn)行工作的。Scott的實例采用循序漸進(jìn)的方式將其編寫為四部分內(nèi)容:
asp.net mvc框架(第1部分)
asp.net mvc框架(第2部分):URL路徑選擇
asp.net mvc框架(第3部分):從控制器將ViewData傳遞到視圖
asp.net mvc框架(第4部分):處理表單編輯和提交場景
mvc框架為開發(fā)者提供了足夠的靈活性去選擇視圖與模型引擎以滿足他們的需要。在Scott的示例中,他使用了LINQ to SQL模型,但Brad決定使用Entity框架,并以Northwind數(shù)據(jù)庫作為數(shù)據(jù)源。
開發(fā)者可以選擇多種不同的模型提供者,例如:
NHibernate
LINQ to SQL
Entity框架
在將來,我們或許還可以看到其他的模型提供者:
SubSonic
LLBLGen Pro
LightSpeed
或者其他為人所知的
Brad的方法是創(chuàng)建一個實例,引導(dǎo)開發(fā)者如何使用asp.net mvc Application and Test創(chuàng)建項目。開發(fā)者需要安裝下列內(nèi)容:
VS2008
asp.net 3.5 Extensions
ADO.NET Entity Framework Tools Dec 07 Preview
Northwind sample database (Northwind.mdf)
ASP.NET MVC實例指南
該指南內(nèi)容廣泛,有很高的學(xué)習(xí)價值,從中可以獲知mvc框架的功能以及如何將它們聯(lián)系在一起。使用mvc不同于以往所開發(fā)的Web Forms應(yīng)用程序,甚至對于經(jīng)驗豐富的asp.net Web Form開發(fā)人員來說,也需要逐漸地習(xí)慣它。
入門
一旦asp.net 3.5 Extensions安裝完畢,就有幾種項目類型可供選擇,其中包括asp.net mvc Web Application以及asp.net mvc Web Application and Test。asp.net mvc被設(shè)計為易于測試的框架,而Brad也使用了測試功能。
File/新建項目 - 選擇asp.net mvc Web Application and Test
它會創(chuàng)建一個單獨的解決方案,包括一個Web應(yīng)用程序項目,并且,該項目可以用做單元測試。它們都是預(yù)先生成的,包含了你需要創(chuàng)建的一些基礎(chǔ)內(nèi)容。
ASP.NET MVC實例:創(chuàng)建Routes
mvc框架中的Routing(路徑選擇)是設(shè)計中非常值得關(guān)注的一項功能。開發(fā)人員可以通過它判斷應(yīng)用程序如何查找頁面。在經(jīng)典的asp.net應(yīng)用程序中,一個頁面例如home.aspx,總會有一個非常清晰的路徑可以訪問該頁面,通常形如www.mywebsite.com/home.aspx。與此比較,Routing為開發(fā)者提供了更多的靈活性。
asp.net mvc提供的其中一個非常強(qiáng)大的新特性是它能夠定制訪問應(yīng)用程序的URLs。顯然,對于磁盤上的物理文件和用來訪問頁面功能的URL而言,URL路徑選擇特性隔離了兩者之間的關(guān)聯(lián)關(guān)系。這對于搜索引擎的優(yōu)化以及提高網(wǎng)站的通用性都是非常重要的。例如,現(xiàn)在我們不需要訪問這樣的地址http://localhost/Products/ItemDetails.aspx?item=42,而是通過http://localhost/Products/CodFishOil進(jìn)行訪問,這樣的URL更讓人賞心悅目。
它的實現(xiàn)是在mvc應(yīng)用程序的global.asax文件中創(chuàng)建一個路徑表。值得慶幸的是,模板中的默認(rèn)內(nèi)容對于應(yīng)用程序而言已經(jīng)足夠了。
- RouteTable.Routes.Add(new Route
- {
- Url = "[controller]/[action]/[id]",
- Defaults = new { action = "Index", id = (string)null },
- RouteHandler = typeof(mvcRouteHandler)});
這段代碼給出了我們針對自己的站點所需要的URLs格式。特別的,格式為
http://localhost/Products/Details/CodFishOil
的URL應(yīng)該轉(zhuǎn)換為ProductsController類(注意,我們?yōu)轭惷砑恿恕癈ontroller”后綴,使得這些類能夠區(qū)別于設(shè)計中的模型對象)。接著,Action是Details類的方法,最后,傳遞給details方法的參數(shù)為CodFishOil。
當(dāng)然,還可能有其它的格式,只需要在URL格式的字符串中修改正則表達(dá)式即可。
ASP.NET MVC實例:創(chuàng)建模型(Model)
模型是大多數(shù)web應(yīng)用程序的心臟,幾乎所有的數(shù)據(jù)都存儲在模型之中。mvc框架允許開發(fā)者幾乎沒有限制地使用任意一種數(shù)據(jù)源,并能夠輕松地在各種數(shù)據(jù)源之間進(jìn)行切換。
模型表示你將要在應(yīng)用程序中使用的數(shù)據(jù)。在本例中,從模型開始應(yīng)用程序的核心開發(fā)是一個不錯的選擇。
將Northwind.mdf文件復(fù)制到mvcApplication的App_Data文件夾中。對于SqlServer而言,Northwind可能是最常用的示例數(shù)據(jù)庫了。你可以從官方地址 中下載它,如果您只需要原始文件的話也可以從這里獲取 。
接下來,我們需要基于northwind數(shù)據(jù)庫創(chuàng)建LINQ模型,以便于進(jìn)行操作。你可以使用NHibernate 、LinqToSql 、Entity框架 ,或者其它的.NET ORM技術(shù)。只要它的返回結(jié)果為.NET對象,asp.net mvc框架就能夠?qū)ζ溥M(jìn)行操作。在本例中,我使用了Entity框架。
右鍵單擊Models目錄,選擇add new item
在對話框中,選擇ADO.NET Entity Data Model。
在向?qū)е?,選擇“Generate from Database”,然后賦予默認(rèn)的“Northwnd”連接字符串。
對于演示而言,我們只需要使用Categories、Products和Supplier數(shù)據(jù)表,當(dāng)然,你也可以擴(kuò)展該演示以引入更多豐富的特性集。但是目前除了這三者之外,不要選擇數(shù)據(jù)庫視圖、存儲過程和其他的數(shù)據(jù)表。
當(dāng)你單擊完成時,VS會創(chuàng)建一組.NET類,這些類定制創(chuàng)建了訪問數(shù)據(jù)庫的相關(guān)內(nèi)容。我們還能夠獲得一個界面友好的設(shè)計器,以可視化方式展現(xiàn)數(shù)據(jù)之間的關(guān)系。
注意,賦予這些類的默認(rèn)名仍然沿用了數(shù)據(jù)庫的復(fù)數(shù)名詞,但是在我們的OR映射中,它們表達(dá)的是單個的實例。為了使得代碼具有可讀性,應(yīng)該將所有的表名修改為準(zhǔn)確的單數(shù)名詞:Category、Product和Supplier。至于Product的Navigation屬性,也需要修改為單數(shù)形式,因為對于產(chǎn)品而言,只有一個類別(Category)和供應(yīng)商(Suppler)。
接下來,我們需要修改命名空間使得代碼更加準(zhǔn)確直觀……右擊設(shè)計視圖,設(shè)置相關(guān)的屬性,例如將namespace設(shè)置為“NorthwindModels”,將Entity Container的名稱設(shè)置為“NorthWindEntities”
雖然我們沒有給出模型的完整示例,但給出的內(nèi)容已經(jīng)足以指導(dǎo)開發(fā)者完成剩余的內(nèi)容……讓我們跳轉(zhuǎn)到下一個話題,來看一看控制器。
ASP.NET MVC實例:創(chuàng)建控制器
控制器是我們應(yīng)用程序的大腦。我們可以將控制器想象為機(jī)場的空中交通控制器,指揮飛機(jī)的進(jìn)出方向。一方面,控制器負(fù)責(zé)獲取數(shù)據(jù),另一方面,它則負(fù)責(zé)將數(shù)據(jù)傳遞到視圖。
右擊Controller目錄,選擇“Add new Item”。在對話框中找到mvc Controller,并確保賦予的名稱是以Controller后綴結(jié)尾的。在我們的例子中將會編寫ProductsController類。
好的,我們現(xiàn)在從ProductsController.cs開始
控制器的目的是為視圖準(zhǔn)備模型對象。我們希望盡可能地將邏輯放到視圖之外,因為它很難在視圖中進(jìn)行測試。因此,在控制器中,我們會訪問模型,并獲得所有創(chuàng)建完畢的模型,這樣,所有視圖所要做的就是輸出某些數(shù)據(jù)。
首先,我們需要訪問數(shù)據(jù)庫。
1. 添加正確的命名空間,包括Linq以及指向我們的OR映射的引用。
- using System.Linq;
- using NorthwindModel;
2、接下來,我們需要創(chuàng)建NorthwindEntities容器類的實例。幾乎所有的action都會訪問這個類。
- public class ProductsController : Controller
- {
- NorthwindEntities Northwind = new NorthwindEntities();
好的,現(xiàn)在我們需要創(chuàng)建第一個action:顯示所有的類別。記住,控制器的職責(zé)是為視圖準(zhǔn)備模型對象。在定義一個新的action時,我喜歡首先編寫一條注釋,以提醒我訪問這一功能的URL是什么。
接下來要做的事情就是從模型中訪問Categories。我將結(jié)果放到一個泛型集合類中(你可能需要添加System.Collections.Generic引用),然后將結(jié)果傳遞到名為“Categories”的視圖中。這是一個非常簡單的例子,后面我們會在此處添加更加復(fù)雜的邏輯。
- //URL: http://localhost/Products/Categories
- [ControllerAction]
- public void Categories()
- {
- List categories = Northwind.Categories.ToList();
- RenderView("Categories", categories);
- }
下一步,我們需要創(chuàng)建“Categories”視圖。
ASP.NET MVC實例:創(chuàng)建視圖
對于視圖而言,mvc框架給了開發(fā)者和模型差不多的靈活性。開發(fā)人員可以選擇一組視圖引擎并對它們進(jìn)行切換。
右擊Views文件夾,添加新的目錄“Products”。這使得我們可以清晰地組織我們的視圖。右擊Views/Products文件夾,然后添加一個新項mvc View Content Page。我們會充分地利用Master Page,它是在默認(rèn)的項目中已生成的,可以使得界面看起來更加友好。
將該頁面命名為Categories.aspx。視圖的名稱非常重要,它必須與之前提及的RenderView方法的第一個參數(shù)相匹配。
默認(rèn)項目會將Master Page放到Views/Shared/Site.Master中
ViewData是我們要從控制器中傳遞的內(nèi)容,為了得到對它的強(qiáng)類型訪問,我們需要告知視圖頁面它所期待的類型。這可以通過打開codebehind文件(Categories.aspx.cs),修改繼承的類型來完成:
- public partial class Categories : ViewPage
- {
- }
修改為:
- public partial class Categories : ViewPage< List >
- {
- }
接著你就可以編寫清晰、簡單、可設(shè)計的HTML了。注意,我在這里將所有從ViewData返回的元素項進(jìn)行了一次循環(huán),然后將它們作為鏈表傳出。我使用了mvc的輔助方法Html.ActionLink,為包含了對應(yīng)產(chǎn)品ID的List action創(chuàng)建了URL。
- < % foreach (var category in ViewData) { %>
- < %= Html.ActionLink(category.CategoryName, new { action="List", id=category.CategoryName }) %>
- < % } %>
ASP.NET MVC實例:瀏覽產(chǎn)品
好的,一切準(zhǔn)備妥當(dāng),可以運行了。
按下F5,導(dǎo)航條上就會出現(xiàn)我們剛才編寫的控制器action:http://localhost:64701/products/Categories
點擊任何一個鏈接都會出現(xiàn)一個錯誤,因為我們還沒有編寫List action。這是我們接下來所要做的。
說句題外話,如果你像我這樣習(xí)慣在開發(fā)的aspx頁面上使用“View in Browser”,你可能會看到這個錯誤。
若要重現(xiàn)此錯誤,請右擊Categories.aspx,然后選擇View in browser。
你會獲得一個錯誤。為什么?是的,請務(wù)必謹(jǐn)記在mvc模型中,所有的執(zhí)行都要經(jīng)過控制器,視圖自身是不能運行的。未來的工具會對此進(jìn)行改進(jìn),但至少在現(xiàn)在,可以對default.aspx使用F5或者通過“run in browser”進(jìn)行操作。當(dāng)然應(yīng)該首先確保你已經(jīng)編譯了解決方案。
List Action視圖現(xiàn)在,讓我們回到之前省略的內(nèi)容:添加List action。在這里我們需要的是在給定的Category中查找所有的產(chǎn)品。首先,我需要從模型中獲取所有產(chǎn)品,然后我必須確保Category的引用已經(jīng)被加載。Entity框架在默認(rèn)情況下提供了一個顯式的加載模型。因此,你必須明確地加載你所需要的所有表。最后,我們再呈現(xiàn)視圖。
- //example URL:http://localhost:64701/products/List/Confections
- [ControllerAction]
- public void List(string id)
- {
- List products = Northwind.GetProductsByCategory(id);
- //prepare the view by explicitly loading the categories
- products.FindAll(p => p.Category == null).ForEach(p => p.CategoryReference.Load());
- RenderView("ListingByCategory", products);
- }
注意,我調(diào)用了NorthwindDataContext類的一個自定義方法。我個人傾向于將所有的數(shù)據(jù)訪問邏輯封裝到這個類中。若要定義該方法,可以右擊Model,通過add new item選擇CodeFile,并命名為NorthwindDataContext.cs,然后給出如下的實現(xiàn)。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- namespace NorthwindModel
- {
- public partial class NorthwindEntities
- {
- }
- }
現(xiàn)在,你可以很容易地為該類添加數(shù)據(jù)訪問方法了,例如我們之前使用的GetProductsByCategory()方法。
- public List GetProductsByCategory(string category)
- {
- return Products.Where(p => p.Category.CategoryName == category).ToList();
- }
下一步,我們需要添加ListingByCategory視圖。遵循前面介紹的相同步驟,我們在Views/Products/目錄下添加ListingByCategory.aspx頁面。
這一次,我們應(yīng)該讓ViewData成為List類型
- public partial class ListingByCategory : ViewPage
- {
- }
接下來實現(xiàn)視圖,我們只是對視圖的數(shù)據(jù)進(jìn)行了循環(huán),并以正確的格式輸出。
- < %--Print out the catagory name--%>
- < % foreach (var product in ViewData) { %>
- < % if (product.Category.CategoryName != null) { %>
- < %=product.Category.CategoryName %>
- < % break; %> < %} //end if %>< %}//end foreach %>
- < % foreach (var product in ViewData) { %>
- < img alt="< %=product.ProductName %>" src="/Content/Images/< %=product.ProductID%>.jpg" />
- < %=product.ProductName %>
- Price: < %=String.Format("{0:C2}", product.UnitPrice)%>
- < % } %>
一旦你在實例項目中添加了/Content/Images目錄,就會獲得如下頁面:
Brad的實例是用C#編寫的,因此Julie Lerman選擇創(chuàng)建了和Brad相似的例子,她使用了VB.NET和AdventureWorksLT數(shù)據(jù)庫 ,并重點關(guān)注了更多高效的Entity框架查詢。Julie指出了她的實現(xiàn)與Brad的重大不同之處。
我的EDM(譯者注:指實體數(shù)據(jù)模型)創(chuàng)建自AdventureWorksLT數(shù)據(jù)庫。
在AW(譯者注:AdventureWorksLT的簡寫)中,SalesOrderHeaders和Customer的關(guān)系與Northwind中Products和Category的關(guān)系相同。因此,在他使用Products的地方,我使用SalesOrderHeaders;在他使用Categories的地方,我使用Customers。
若要輕易地獲取數(shù)據(jù)并將其傳給視圖,則其中一個關(guān)鍵是我們需要傳遞“一個”對象(而且不是匿名類型)到視圖。然而,對于Order列表(每一個都具有Customer的名字)和Details列表(每一個都具有從Order和Customer中獲得的數(shù)據(jù))而言,我們真正需要的是一個對象圖。
【編輯推薦】