ASP.NET MVC框架(第三部分): 把ViewData從控制器傳到檢視

  • 23092
  • 0
  • 2011-07-09

摘要:ASP.NET MVC框架(第三部分): 把ViewData從控制器傳到檢視

【原文位址】ASP.NET MVC Framework (Part 3): Passing ViewData from Controllers to Views
【原文發表日期】 Thursday, December 06, 2007 2:49 AM
【譯注】根據Scott Guthrie原文的回復,ASP.NET MVC框架的第一個CTP將於12月7日發佈

過去的幾個星期內,我一直在寫著討論我們正在開發的新ASP.NET MVC框架的系列文章。ASP.NET MVC框架是個你可以用來結構化你的ASP.NET web應用,使之擁有清晰的關注分離,方便你單元測試程式碼和支援TDD流程的可選方法。

這個系列的第一篇建造了一個簡單的電子商務產品列表/瀏覽網站。它討論了MVC後面的高層次的概念,示範了如何從頭建立一個新的ASP.NET MVC專案,實現和測試這個電子商務產品列表功能。系列的第二篇對ASP.NET MVC框架的URL路徑選擇(routing)架構做了深入探討,討論了它的工作原理以及你如何使用它來處理更高級的URL路徑選擇場景。

在今天的文章裡,我將討論控制器是如何與檢視做交互的,具體來說,我將討論你可以把資料從控制器傳到檢視以顯示傳回到客戶端的回復的各種方式。

第一部分的扼要簡述

在這個系列的第一部分,我們建立了一個電子商務網站,實現了基本的產品列表/瀏覽支援。我們是用ASP.NET MVC框架實現這個網站的,這個方法會很自然地將程式碼結構化為獨特的控制器,模型和檢視元件。

當瀏覽器向我們的網站發送一個HTTP請求時,ASP.NET MVC框架將使用它的URL路徑選擇引擎,把進來的請求映射到一個控制器上的action方法來處理它。在基於MVC的應用中的控制器負責處理進來的請求,處理用戶輸入和交互,執行基於這些輸入和交互的應用邏輯(獲取或更新存儲在資料庫中的模型資料等等)。

到生成傳回到客戶端的HTML回復的時候,控制器一般是與「檢視」元件合作,這些檢視元件是以獨立於控制器的單獨的類或模板的形式實現的,其目的是完全注重於封裝顯示邏輯。

檢視不應該含有任何應用邏輯或資料庫訪問程式碼,所有的應用/資料邏輯應該由控制器類來處理。這麼劃分的動機是幫助強制你的應用/資料邏輯與介面生成程式碼間的清晰分離。同時這也方便你獨立於你的介面顯示邏輯來單元測試你的應用/資料邏輯。

檢視應該只使用從控制器傳過來的特定於檢視的資料來生成輸出。在ASP.NET MVC框架中,我們稱這個特定於檢視的資料為「ViewData」。這個部落格的其他部分將討論你可以用來將ViewData從控制器傳遞給檢視來生成顯示的一些不同方法。

一個簡單的產品列表場景

為幫助說明我們可以用來把ViewData從控制器傳遞給檢視的一些技術,讓我們來建造一個簡單的產品列表網頁:

我們將用一個CategoryID整數來過濾我們想要顯示在頁面上的產品。注意上面我們是如何把CategoryID嵌在URL中的(例如,Products/Category/2 或 /Products/Category/4 )。

然後,我們的產品列表網頁顯示了2個不同的動態內容元素。第一個元素是我們要顯示的分類的文字名稱(例如,Condiments-調味品),第二個元素是一個HTML <ul><li/></ul> 產品名字列表。我在上面的螢幕截圖中對這2個元素用紅筆畫了圈。

在下面,我們將看一下我們可以使用的2個不同的方法來實現ProductsController類別,這個類別處理進來的請求,獲取處理請求所需的資料,然後將這個資料傳給一個List檢視來顯示。我們要研究的第一個方法是用後期繫結的字典物件傳遞這個資料,第二個方法則使用強型別類的方式來傳遞這個資料。

方法 1:使用 Controller.ViewData 字典來傳遞ViewData

Controller基類有個ViewData字典屬性,可以用來填充你要傳給檢視的資料。你使用鍵/值模式將物件加入 ViewData 字典。

下面是個ProductsController類,其中的Category action方法實現了我們上面的產品列表場景。注意,它是如何使用分類的ID參數來查詢該分類的文字名稱,以及獲取該分類中的產品列表的。它使用「CategoryName」和「Products」兩個鍵將這兩個資料存儲在Controller.ViewData 集合中:

然後,我們上面的Category action方法叫用 RenderView(「List」) 來表示它要用哪個模板來做顯示。當你像這樣叫用RenderView時,它會將ViewData字典傳給檢視,以顯示對應的回復。

實現我們的檢視

我們將使用居於我們專案的\Views\Products目錄下的List.aspx文件來實現我們的List檢視。這個 List.aspx 將繼承 \Views\Shared 資料夾中的Site.Master母版頁中的佈局(在你建立一個新的檢視網頁時,你可以在 VS 2008 中,右擊,選擇添加新項->MVC檢視內容網頁來接連一個母版頁):

當我們使用MVC檢視內容網頁模板來建立List.aspx網頁時,它不是從通常的 System.Web.UI.Page 類別繼承而來,而是從System.Web.Mvc.ViewPage 基礎類別繼承而來(是現有的Page類的一個子類別):

ViewPage基類提供一個ViewData字典屬性,我們可以在檢視網頁裡訪問由控制器添加的資料物件。然後我們可以取出這些資料物件,使用它們來顯示HTML輸出,可以用伺服器控制項的方式,或者用 <%= %> 顯示程式碼的方式。

使用伺服器控制項來實現我們的檢視

下面是一個如何使用現有的<asp:literal> 和 <asp:repeater>伺服器控制項來實現我們的HTML介面的例子:

我們可以用下面的後台程式碼類將 ViewData 繫結到這些控制項之上(注意我們是如何使用ViewPage的ViewData字典來實現的 ):

註: 因為頁面上沒有 <form runat="server">,是不會輸出 view-state 的。上面的控制項也不會自動生成任何ID值,這意味著你對輸出的HTML有完全的控制。

使用 <%= %> 程式碼來實現我們的檢視

如果你更喜歡使用行內程式碼來生成輸出的話,你可使用下面的 List.aspx 來實現跟上面完全一樣的結果:

註: 因為ViewData的型別是含有「objects」的字典,為了對它使用foreach語句,我們需要將ViewData["Products"]的型別轉換成 List<Product> 或者 IEnumerable<Product>。我在頁面上引用了System.Collections.Generic 和 MyStore.Models 命名空間 以避免輸入 List<T> 和 Product 型別的完整名稱。

注: 上面使用了「var」關鍵詞,這是VS 2008中新的 C# 和 VB 「型別推斷」特性的一個
例子(在這裡閱讀我以前的相關文章)。因為我們將ViewData["Products"] 轉換成了 List<Product>,我們在 List.aspx 文件中的 prduct 變數上得到了完整的intellisense:

方法 2:使用強型別類來傳遞ViewData

除了支援後期繫結的字典方法外,ASP.NET MVC框架還允許你把強型別的ViewData物件從控制器傳遞給你的檢視。使用這個強型別的方法有幾個好處:

  1. 避免使用字串來查詢物件,得到對你的控制器和檢視程式碼的編譯時檢查
  2. 避免需要在使用象C#這樣的強型別語言中明確轉換ViewData物件字典中的值
  3. 在你的檢視網頁的標籤文件以及後台程式碼文件中得到你的ViewData物件的自動程式碼intellisense
  4. 可以使用程式碼重構工具來幫助自動化對整個應用和單元測試程式碼庫的改動

下面是一個強型別的ProductsListViewData類別,封裝了 List.aspx 檢視顯示我們的產品列表所需的資料,它含有 CategoryName 和 Products 屬性(是通過使用新的C#自動屬性支援來實現的):

然後我們可以更新我們的 ProductsController 實現來使用這個物件,把一個強型別的ViewData物件傳給我們的檢視:

注意上面,我們是如何通過 RenderView() 方法的一個額外的參數,把我們的強型別 ProductsListViewData 物件傳給View的。

把檢視的ViewData字典與強型別的ViewData物件一起使用

前面我們編寫的 List.aspx 檢視實現會繼續和我們更新過的 ProductsController 協作,不需改動程式碼。這是因為,當把一個強型別的 ViewData 物件傳遞給繼承自 ViewPage 的檢視類別時,ViewData 字典會自動使用反射對強型別的物件的屬性做查詢取值。所以我們像下面這樣的檢視中的程式碼:

會自動使用反射來從強型別的 ProductsListViewData 物件中獲取 CategoryName 屬性,這個物件是我們在叫用 RenderView 方法時傳入的。

使用ViewPage<T>基類來對ViewData強型別化

除了支援基於字典的ViewPage基類外,ASP.NET MVC框架中還發佈有基於泛型的 ViewPage<T> 實現。如果你的檢視是從 ViewPage<T> 繼承而來,這裡T表示是控制器傳給檢視的 ViewData 的型別,那麼 ViewData 屬性就將是使用了這個T類的強型別屬性。

例如,我們可以更新我們的 List.aspx.cs 後台程式碼類別,不是從ViewPage繼承來,而是繼承自 ViewPage<ProductsListViewData> :

這麼做之後,頁面上的 ViewData 屬性將會從一個字典變成屬於 ProductsListViewData 型別。這意味著,我們現在可以不再使用基於字串的字典來查閱獲取資料,而是可以使用強型別的屬性了:

然後,我們可以使用伺服器控制項的方法,或者 <%= %> 顯示的方法來生成基於這個ViewData的HTML。

使用伺服器控制項來實現 ViewPage<T>檢視

下面是一個例子,我們可以使用<asp:literal> 和 <asp:repeater>伺服器控制項來實現我們的HTML介面。這是我們使用繼承自 ViewPage 的 List.aspx 網頁時所使用的完全一樣的標籤:

下面是相應的後台程式碼。注意,因為我們是從 ViewPage<ProductsListViewData> 繼承而來的,我們可以直接訪問它的屬性,而不要對任何東西做型別轉換(什麼時候我們決定對其中一個屬性改名的話,我們還將得到重構工具的支援):

使用 <%= %> 程式碼實現我們的 ViewPage<T> 檢視

如果你更喜歡使用行內程式碼來生成輸出的話,你可以像下面這樣在 List.aspx 中達成跟上面一樣的結果:

使用 ViewPage<T> 方法,我們現在不再需要對 ViewData 使用字串查閱了。更重要的是,注意上面,我們不再需要對任何屬性做型別轉換了,因為它們已經是強型別的。這意味著,我們可以編寫 foreach (var product in ViewData.Products) ,而不用對 Products 做型別轉換。我們還在迴圈中的 product 變數上得到了完整的intellisense:

結語

希望本文章提供了關於控制器如何把資料傳遞給檢視以顯示傳回到客戶端的回復的一些細節。你可以使用後期繫結的字典,或者使用強型別的方式來達成這個目的。

第一次試著建造MVC應用時,你很可能發現把應用控制器的邏輯和生成介面的程式碼分離開來的概念有點怪。你大概要花上一段專門的時間來多建造些應用,你才會感到習慣,把自己的思路轉向到處理一個請求,執行所有的應用邏輯,把建造介面回復所需的 viewdata 包裝起來,然後交給單獨的一個檢視頁面去顯示的觀念上去。 重要事項:如果這個模型對你來說並不感覺舒服,那麼別用它,MVC的方法純粹是可選的,我們並不認為這是每個人都想要用的東西。

但這個劃分應用的好處以及其後的目標在於,它允許你獨立於你的介面顯示程式碼,來運行和測試你的應用和資料邏輯。這極大地方便你為你的應用開發全面的單元測試,以及在建造應用時使用TDD(測試驅動開發)的流程。在以後的文章裡,我會對此做更深入的討論,以及討論你可以用來輕鬆測試程式碼的最佳實踐。

希望本文對你有所幫助,

Scott

標籤: , ,