【原文地址】ASP.NET MVC Framework (Part 4): Handling Form Edit and Post Scenarios
【原文發表日期】 Sunday, December 09, 2007 4:42 AM
過去的幾個星期內,我一直在寫著討論我們正在開發的新ASP.NET MVC架構的系列貼子。ASP.NET MVC架構是個你可以用來結構化你的ASP.NET web應用,使之擁有清晰的關注分離,方便你單元測試代碼和支援TDD流程的可選方法。
這個系列的第一篇建造了一個簡單的電子商務產品列表/瀏覽網站。它討論了MVC後面的高層次的概念,示範了如何從頭建立一個新的ASP.NET MVC項目,實現和測試這個電子商務產品列表功能。系列的第二篇對ASP.NET MVC架構的URL直接選取(routing)架構做了深入探討,討論了它的工作原理以及你如何使用它來處理更進階的URL直接選取情境。 第三篇討論了控制器是如何與視圖做互動的,特別地討論了你可以把視圖資料從控制器傳給視圖以顯示返回到用戶端的回複的各種方法。
在今天的文章裡,我將討論你可以用MVC架構來處理表單輸入和提交情境的各種方法,以及討論一些你可以用來簡化資料編輯情境的HTML輔助方法。點擊這裡下載我們將在下面為解釋這些概念而建造的完整的應用的原始碼。
表單輸入和提交情境
為示範如何在ASP.NET MVC架構中處理表單輸入和提交情境的一些基本原則,我們將建造一個簡單的產品列表,產品產生,和產品編輯情境。它將擁有三個核心的使用者體驗:
按類列出的產品列表
通過導航到/Products/Category/[CategoryID] 這樣的URL,使用者將能看到在某個特定產品分類內的所有產品的列表:
添加新產品
使用者將能通過點擊上面的“添加新產品”的連結往商店裡添加一個新產品。點擊之後,會轉到/Products/New URL,在這裡,系統將提示使用者輸入要添加的新產品的細節:
在點擊Save(儲存)之後,產品就會添加到資料庫中,然後就會轉向返回到產品列表網頁。
編輯產品
在產品列表網頁上,使用者可以點擊每個產品旁邊的“Edit”(編輯)連結。這會轉到/Products/Edit/[ProductID] URL,在這裡,使用者可以改動產品的細節,然後點擊Save按鈕,往資料庫裡更新:
我們的資料模型
我們將使用SQL Server Northwind樣品資料庫來儲存我們的資料。然後我們將使用.NET 3.5內建的LINQ to SQL對象關係映射器(ORM)來對Product, Category, 和 Supplier對象進行建模,這些對象代表了我們的資料庫資料表中的記錄行。
一開始,在ASP.NET MVC項目中,右擊/Models子目錄,選擇“添加新項” -> “LINQ to SQL 類”,調出 LINQ to SQL ORM 設計器來對我們的資料對象建模:
然後我們將在項目中建立一個NorthwindDataContext部分類(partial class),向裡面添加一些輔助方法。我們定義這些輔助方法有2個原因: 1)避免在我們的Controller類中直接嵌入我們的LINQ查詢,2) 將允許我們在將來更容易地改變我們的控制器以使用dependency injection(依賴注入)。
我們將添加的NorthwindDataContext輔助方法是象下面這樣的:
想進一步瞭解LINQ和LINQ to SQL的話,請參閱我這裡的LINQ to SQL系列。
建造我們ProductsController控制器
我們將使用單一控制器類來實現這三個核心使用者瀏覽體驗,我們將稱這個控制器類為“ProductsController”(在Controllers子目錄上右擊,選擇“添加新項” -> “MVC 控制器”來建立這個類:
我們的 ProductsController 類將通過實現"Category", "New", 和"Edit" 等action方法來處理象/Products/Category/3, /Products/New, 和/Products/Edit/5這樣的URL:
想瞭解這些URL是如何導向到 ProductsController 類的action方法上的話,請閱讀我的ASP.NET MVC系列的第一部分和第二部分。在本文的例子裡,我們將使用預設的/[Controller]/[Action]/[Id]路徑映射規則,這意味著我們不必配置什麼東西,路徑導向就會自動發生。
我們控制器的Action方法將使用三個視圖網頁,用以顯示輸出。"List.aspx", "New.aspx", 和 "Edit.aspx" 網頁將居於 \Views\Products 子目錄下,這些網頁將基於\Views\Shared目錄中的Site.Master主版頁面上。
實現按類列出的產品列表
我們要實現的網站的第一部分將是產品列表URL (/Products/Category/[CategoryId]) :
我們將使用我們的ProductsController類上的"Category" action方法來實現這個功能。我們將使用LINQ to SQL DataContext類,和我們往其中添加的GetCategoryById輔助方法,來擷取一個Category對象,該對象代表了由URL (譬如, /Products/Category/3) 指定的某個特定分類。然後我們將該Category對象傳給"List"視圖來從中產生回複:
在實現我們的List視圖時,我們首先將更新我們網頁的後台代碼,從ViewPage<Category>繼承而來,這樣頁面的ViewData屬性將是從我們的控制器傳過來的Category對象的類型(第三部分對此有詳細討論):
然後我們將象下面這樣實現List.aspx:
上面的視圖在頁面上方顯示了分類名稱,然後顯示了分類內的所有產品的項目列表。
在項目列表的每個產品旁邊,有個 "Edit" 連結。我們是用在第二部分中討論過的Html.ActionLink輔助方法來顯示這些HTML超連結(譬如,<a href="/Products/Edit/4">Edit</a>)的,在"Edit"連結被點擊後,使用者將被導向到 "Edit"action方法。然後我們還將使用Html.ActionLink輔助方法在頁面底部產生一個<a href="/Products/New">Add New Product</a>連結,在該連結被點擊後,使用者將被導向到"New" action方法。
當我們訪問 /Products/Category/1 URL時,在瀏覽器中查看源碼的話,你會注意到我們的ASP.NET MVC應用輸出了非常乾淨的HTML和URL標識:
實現添加新產品(第一部分-背景知識)
現在讓我們來實現網站的“添加新產品”表單提交功能,最終我們想要使用者在訪問/Products/New URL時看到象下面這樣的顯示:
在ASP.NET MVC架構中,表單輸入和編輯情境一般是通過在Controller類上呈示2個Action方法來處理的。第一個Controller Action方法負責發送含有要顯示的初始表單的HTML。第二個Controller Action方法則負責處理從瀏覽器發回的任何錶單提交。
例如,對上面的“添加產品”螢幕,我們會選擇在ProductsController上的2個不同action中來實現:一個叫"New",另一個叫"Create"。/Products/New URL負責顯示一個帶有HTML文字框和下拉框控制項的空白表單,讓使用者輸入新產品的細節。然後,這個網頁上的HTML <form>元素將其action屬性設定為 /Products/Create URL。這意味著當使用者點擊表單提交按鈕時,表單的輸入將被發送到"Create" action方法上來處理和更新資料庫。
實現添加新產品(第二部分 - 第一種方法)
下面是我們可以用來實現ProductsController的一個初始實現。
注意上面,在涉及產品產生過程中,我們有2個action方法, - "New" 和 "Create"。 "New" action方法只是簡單地向使用者顯示一個空白表單。"Create" action方法則處理從表單提交過來的值,根據這些值在資料庫中產生一個新產品,然後將客戶轉向到產品的分類列表網頁。
發送到用戶端的HTML表單,是在由"New" action方法調用的"New.aspx"視圖裡實現的。這個視圖的一個初始實現(每個輸入都用了文字框)看上去象下面這樣:
注意上面,我們在網頁上使用了標準的 HTML <form> 元素,而不是form runat=server。表單的"action"屬性被設定為ProductsController上的"Create" action方法。在頁面底部的<input type="submit">元素被點擊時,提交就會發生,之後,ASP.NET MVC架構就會自動將ProductName, CategoryID, SupplierID 和 UnitPrice值對應為方法參數,傳給ProductsController上的 "Create" action方法:
至此,我們運行網站時,就有了最基本的產品輸入功能:
實現添加新產品 (第三部分 - 使用HTML輔助方法實現下拉框)
我們在前面一節裡建立的產品輸入螢幕是可行的,但不是很友好。具體來說,它要求使用者知道正輸入的產品的原始CategoryID和SupplierID成員。我們需要通過顯示內含可讀名稱的HTML下拉框來修正這個問題。
第一步,將修改ProductsController來向視圖裡傳人2個集合,一個內含現有的分類列表,另一個內含產品供應商列表。我們將通過產生一個封裝這些列表的強型別的ProductsNewViewData類,然後將它傳給視圖來達成這個目的(你可以在第三部分中瞭解有關詳情):
然後我們將更新 "New" action 方法來填充這些集合,然後將它們作為ViewData傳給 "New" 視圖:
然後在我們的視圖裡,我們可以使用這些集合來產生 HTML <select> 下拉框。
ASP.NET MVC HTML 輔助方法
我們可以用來產生下拉框的一個方法是在HTML裏手工產生內含 if/else 語句的 <% %> for-迴圈。這會給與我們對HTML的完全控制,但HTML會很亂。
一個你可以使用的乾淨得多的方法是利用ViewPage基類上的"Html"輔助屬性。這是個方便對象,呈示了一套HTML輔助介面方法,用於自動化 HTML介面的產生。例如,在本文章的前面,我們使用了 Html.ActionLink輔助方法來產生 <a href=""> 元素:
HtmlHelper對象(以及我們將在以後的教程裡討論的AjaxHelper對象)是特地設計可以通過使用"擴充方法"(VS 2008中VB和C#的一個新語言特性)來輕鬆地擴充的。這意味著,任何人都可以為這些對象產生他們自己的自訂輔助方法,共用這些方法,為你所用。
在ASP.NET MVC架構將來的預覽版中,我們將提供幾十個內建的HTML和AJAX輔助方法。在第一個預覽版中,只有"ActionLink"方法是內建於 System.Web.Extensions(目前實現核心ASP.NET MVC架構的程式集)中的。但我們還將有一個單獨的 "MVCToolkit" 下載,你可以加到你的項目中,來得到你可以在第一個預覽版中使用的的幾十個輔助方法。
要安裝MVCToolkit HTML輔助方法的話,只要將MVCToolkit.dll程式集添加為你的項目的引用即可:
重新編譯你的項目,然後下一次你鍵入 <%= Html. %> 的話,你將看到許許多多你可以使用的額外介面輔助方法:
為產生HTML <select>下拉框,我們可以使用Html.Select()方法。每個方法都有重載的版本,在視圖裡有完整的intellisense:
我們可以更新我們的"New"視圖,用下面的代碼,使用Html.Select選項來顯示使用CategoryID/SupplierID屬性作為值,CategoryName/SupplierName作為顯示文字的下拉框:
這會在運行時為我們產生適當的<select> HTML標識:
在/Products/New螢幕上給使用者一個方便的方式來選擇產品分類和供應商:
註: 因為我們還是在向伺服器提交CategoryID和SupplierID值,所以我們根本不用更新ProductsController的Create Action方法來支援這個新的下拉框介面,這個方法還是工作的。
實現添加新產品(第四部分 - 使用UpdateFrom方法清理Create代碼)
我們的ProductsController的"Create" Action方法負責處理我們的“添加產品”情境的表單提交。目前它是以action方法參數的方式來處理進來的表單參數的:
這個方法是可行的,但對於涉及大量值的表單,Action方法的簽名就會開始變得有點難讀。而且,上面將所有進來的參數值設定到新的Product對象上的代碼有點長,而且單調。
如果你引用了MVCToolkit程式集,你可以利用在System.Web.Mvc.BindingHelpers命名空間下實現的一個有用的擴充方法,來對此代碼作些清理。這個擴充方法叫做“UpdateFrom”,可以用在任何 .NET 對象上。它接受一個字典作為參數,然後,它會對任何匹配該對象的公開屬性的鍵,自動對本身進行屬性賦值。
例如,我們可以重寫我們上面的Create action方法,來使用UpdateFrom方法,象這樣:
註: 如果你因為安全的原因,想要更明確些,只允許某些屬性可以更新的話,你還可以向UpdateFrom方法傳入一個可以更新的屬性名稱的字串數組:
實現編輯產品功能(第一部分 - 背景知識)
現在讓我們來實現網站“編輯產品”的功能。我們最終想要使用者在訪問/Products/Edit/[ProductID] URL時看到象下面這樣的螢幕:
跟上面的“添加新產品”表單提交例子一樣,我們將使用2個ProductsController Action方法來實現這個表單編輯互動,我們將稱這2個方法為"Edit"和"Update":
"Edit" 會顯示產品表單,"Update"會被用來處理表單的提交行動。
實現編輯產品功能(第二部分 - Edit Action)
我們將通過實現ProductController的Edit action方法來開始啟用我們應用的編輯功能。當我們在本貼子的開頭建立產品列表網頁的時候,我們是這麼建造的,Edit action將接受一個作為URL一部分的id參數(譬如,/Products/Edit/5):
我們想要Edit Action方法從資料庫中擷取適當的產品對象,以及現有的產品供應商和分類集合(這樣,我們可以在我們的編輯檢視裡實現這些東西對應的下拉框)。我們將使用下面的ProductsEditViewData對象來定義一個強型別的視圖對象來代表所有這些資料:
然後,我們可以實現我們的Edit action方法來填充這個viewdata對象,在"Edit" 視圖中顯示:
實現編輯產品功能(第三部分 - Edit 視圖)
我們可以使用下述方法來實現Edit.aspx視圖網頁:
注意我們是如何同時使用上面例子中的Html.TextBox和Html.Select輔助方法來的。這2個方法都是來自MVCToolkit.dll程式集中的擴充方法。
注意Html.Select輔助方法有個重載版本,允許你指定下拉框中的選定值是什麼。在下面的代碼片斷中,我表示我要Category下拉框根據編輯產品目前的CategoryID值自動選擇某一項:
最後,注意我們是如何使用Url.Action()輔助方法來設定<form>元素的action屬性的:
Url.Action和Html.ActionLink這2個輔助方法都使用了ASP.NET MVC架構的直接選取引擎來產生URL(參閱第二部分以瞭解URL產生原理的細節)。這意味著,如果我們改變我們網站的編輯功能的直接選取規則的話,我們不需要改動控制器或視圖中的任何代碼。例如,我們可以將我們的URL做重新對應,換掉/Products/Edit/1,而是使用象/Products/1/Edit這樣更具RESTful的URL的話,上面的控制器和視圖代碼不用做改動,而依舊會工作。
實現編輯產品功能(第四部分 - Update Action)
最後一步是實現ProductController類上的"Update" action方法:
跟前面的"Create" action方法一樣,我們將利用"UpdateFrom"擴充方法來從請求中自動填滿我們的產品對象。但注意,填充的不是一個Null 物件,我們使用了一個模式,先從資料庫中擷取老的值,然後對它應用使用者做的改動,然後更新到資料庫中。
編譯完畢之後,我們重新定向到產品列表網頁,自動化佈建 /Products/Category/[CategoryID],以匹配我們正在操作的產品的儲存的狀態。
結語
希望本文章提供了在ASP.NET MVC架構中如何處理表單輸入和提交情境的一些細節,還提供了你可以如何處理和結構化常見資料輸入和編輯情境的一些背景。
點擊這裡下載一個內含我們在上面建造的完整應用原始碼的.ZIP 檔案。
在將來的文章裡,我將討論如何處理表單輸入和編輯情境中資料驗證和錯誤複原的情形。我將討論一些促進快速應用開發的內建的資料和安全支架(scaffolding)。我將討論你如何在MVC架構中使用ASP.NET AJAX進行啟用AJAX的編輯。我還將對如何單元測試控制器和向控制器添加依賴注入做深入的探討。