上週,我正在閱讀一些有關使用WCF,實體框架以及如何在整個服務邊界運輸實體的文章。我遇到的文章之一包括一個演示項目,該項目使用模型視圖主持人(MVP)模式。
探索了演示項目後,我認為撰寫有關此模式的文章可能很有趣。您最喜歡的搜索引擎會很樂意為您提供多種鏈接到其他文章的鏈接,這些鏈接可以徹底解釋這種模式。
在本文中,我決定提供具體的實施,而減少關注模式背後的理論。
讓我們滾...
當然,需要一些理論,所以讓我們擺脫困境。
您可以從名稱中扣除MVP模式由三個不同的部分組成:模型,視圖和演示者。這些部分中的每一個都在建立演示文稿,業務和數據訪問層之間的關注點分離中起著自己的作用。
該模型負責處理數據訪問,主持人與模型進行通信並傳遞數據。該視圖從演示者接收數據並將數據傳遞回,它永遠不會與模型直接通信。主持人是視圖和模型之間的介於之間。
圖1 -MVP模式互動

上圖將視圖描述為實現接口。演示層是ASP.NET,Winforms或WPF應用程序需要實現一個或多個查看接口。演示者依次通過此接口與視圖實現進行通信,它對實際實現本身一無所知。
這提供了鬆散的耦合,並防止您的代碼依賴於演示層所使用的技術。這個想法是,您應該能夠替換此層而不會影響您的業務和數據訪問邏輯。
這似乎似乎有些模糊,但是一旦我們繼續進行以下幾點,就應該清除這種模式的實際實現。對於示例,將使用ASP.NET應用程序。
首先,最合乎邏輯的地方是模型。由於它負責處理數據訪問和存儲,因此我們首先需要建立物理數據存儲。為此,我使用了SQL Server 2005 Express,創建了一個名為Southwind的新數據庫,並添加了一張名為“客戶”的桌子。該桌子有3個字段,即:
就是這樣的數據庫。讓我們解僱Visual Studio 2008,並創建一個名為Avppattern的新空白解決方案。接下來添加一個名為“數據庫”的類庫。
通常,我會給該項目一個名字,以“ Company.product.Library”模式,但為了簡單起見,讓我們簡短而簡單。還將項目添加到解決方案後,還刪除自動化的class1.cs文件。
讓我們使用實體框架(EF)從數據庫創建模型。因此,請確保您使用的是Visual Studio 2008,並為Visual Studio和.NET Framework安裝了Service Pack 1。您可以在此處下載服務包。
通過從“解決方案資源管理器”中項目的上下文菜單中選擇“ add,ado.net實體數據模型”,將實體框架數據模型添加到類庫中。 Visual Studio現在將顯示實體數據模型嚮導。命名Southwind的模型,讓Visual Studio為您生成模型。當詢問您要在模型中包含哪些數據庫對象時,只需從表節點中選擇客戶表即可。
說明:如果您不熟悉使用實體框架生成數據模型,我強烈建議亞歷克斯·詹姆斯(Alex James)的培訓視頻。它向您展示瞭如何從頭開始構建簡單的實體數據模型。
圖2顯示了結果實體數據模型。該模型不能比這簡單得多。這樣做是故意這樣做的,以使事情盡可能簡單,並保持對MVP模式的關注。
圖2-實體數據模型

在生成數據模型後,請確保將您的EntityTypes和EntitySets重命名為適當的名稱。經驗法則是將單個名詞用於實體類型和實體組的複數。因此,在這種情況下,我們的EntityType應命名為客戶和EntitySet客戶。 EntityType的名稱已經可以,因為它從客戶表中繼承了它,因此只需選擇客戶EntityType並調整其實體設置名稱屬性即可。
圖3-實體集名稱

實體框架將自動為客戶表生成部分類。如果您願意,您可以選擇擴展此部分客戶類。為了防止您的自定義添加在再生模型時被刪除,將此代碼放在單獨的類文件中。這是與強鍵入數據集合作的模擬。對於本文,這是不需要的。
圖4-解決方案資源管理器

現在,該模型已經到位,讓我們在它的基礎上添加一個業務層,我們可以在其中定義自定義業務邏輯。為了簡單起見,我將在此層中保留代碼限制的量。
請注意,這些層僅強制執行邏輯分離(N層),而不是物理分離(N層)。這些圖層都位於同一台機器上,儘管您當然可以選擇在多個機器 /層上拆除它們,並創建真正的分佈式或N層應用程序。
為了設置業務層,將新的類庫添加到解決方案中並將其稱為業務。接下來,將默認class.cs文件重命名為customermanager.cs。還將對先前創建的類庫數據庫的引用添加到System.Data.Entity Assembly。
清單1顯示CustomAnager類,其中包含一些業務邏輯,用於與實體數據模型(EDM)的客戶實體合作。該代碼幾乎是不言自明的。
清單1-定制班級課程
using System . Collections . Generic ;
using System . Linq ;
using Database ;
namespace Business
{
public class CustomerManager
{
private readonly SouthwindEntities context ;
#region Constructor(s)
public CustomerManager ( )
{
context = new SouthwindEntities ( ) ;
}
#endregion
#region Methods
// Retrieve a generic list of Customer entities.
// This method will return all the customers found in the Customer table.
public List < Customer > GetCustomers ( )
{
var q = from c in context . Customers
select c ;
return q . ToList ( ) ;
}
#endregion
}
}圖5-更新的解決方案

備註:設置N層應用程序將包括引入演示層調用的服務層。然後,服務層利用業務層中的業務對象。演示文稿和業務層之間沒有直接關聯,服務層充當了中間體。在將來的文章中,我將通過展示如何在整個服務邊界上運輸EF實體來解決這一問題。
該業務層僅包含一種有用的方法,即“列表getCustomers()”。 MVP模式中的主持人將在CustomManager業務對像上調用此方法,以便將數據傳遞給視圖。
示例應用程序僅使用MVP模式顯示客戶列表。這可能有點過分殺傷,但是它可以像設計一樣簡單。這個“ Hello World!”的主要目標是應用程序的類型是在如何實現此模式的情況下了解想法。應用程序提供的實際功能並不重要。
實際視圖實現(ASPX頁面,Winforms,WPF ...等)將需要實現視圖接口。視圖實現需要創建演示者的實例,並將其作為構造函數中的參數傳遞。主持人的構造函數具有一個參數,即視圖接口的類型。
列表2列出了IView接口,我們將在ASP.NET ASPX頁面中盡快實現。它有一個名為preparview的事件。 PreparView事件使用簽名的委託指定其沒有返回而沒有參數。
該視圖只能提出這類“空事件”,以表明應該執行某些動作。在這種情況下,該行動表示主持人應刷新視圖實施所維護的客戶列表。主持人可以通過ILIST客戶屬性訪問此客戶列表,該屬性被聲明為接口的一部分。
清單2-查看接口
public delegate void VoidEventHandler ( ) ;
public interface IView
{
event VoidEventHandler PrepareView ;
IList < Customer > Customers { set ; }
}要將視圖接口添加到您的項目中,首先將新的類庫項目添加到稱為演示文稿的解決方案。接下來添加一個新的接口,然後復制並粘貼上述列表中顯示的代碼。演示代碼庫還將包含演示者及其實現的接口。
我將視圖接口和演示者分開在單獨的代碼庫中,以便您可以在多個“查看框架”(例如ASP.NET,Winforms,wpf ...等)之間輕鬆共享它們。圖6顯示了我如何選擇組織此代碼庫。
圖6-更新的解決方案

不要忘記將數據庫和業務項目以及System.Data.Entity Assembly的引用添加。
對於MVP模式的最後部分,我們需要提供主持人。列表3中顯示的CustomersPresenter類引用其構造函數中的IView實現。這樣,它可以與視圖進行通信,而無需實際了解實際實現。正是這種鬆散的耦合使MVP模式非常適合不同的“視圖框架”。
同樣在構造函數中,視圖接口的所有事件都將連接到事件處理程序。在這種情況下,只有一個事件。準備瀏覽事件已連接到View_prepareview事件處理程序。這反過來稱其為主持人的私有方法getCustomers(),該方法返回“刷新”客戶收集,並將其分配給視圖實施維護的客戶收集。
列表3-客戶班級課程
public class CustomersPresenter : ICustomersPresenter
{
#region Fields
private readonly IView view ;
#endregion
#region Constructor(s)
public CustomersPresenter ( IView view )
{
// Save a reference to the view
this . view = view ;
// Hook up an event handler for the events of the view
view . PrepareView += view_PrepareView ;
}
#endregion
#region Private methods
private List < Customer > GetCustomers ( )
{
return new CustomerManager ( ) . GetCustomers ( ) ;
}
#endregion
#region ICustomersPresenter Members
public virtual void view_PrepareView ( )
{
view . Customers = GetCustomers ( ) ;
}
#endregion
}上面的主持人還實現了接口。在示例源代碼中,該接口是空的。我只是出於說明目的將其放在那裡。如果需要的話,您可以充實該界面。例如,您可能需要它作為您喜歡的單元測試框架來模擬演示者。
因此,在我們的情況下,視圖或ASPX頁面只需要實現視圖接口並觸發準備視圖事件,以便從演講者接收更新的客戶列表。視圖本身不會直接與數據庫或業務層進行通信。主持人處理與業務層的通信,該業務層通過解決模型(如果願意的話)來檢索數據(或數據訪問層)。
為了完成本文,讓我們看看這一切如何在ASP.NET演示項目中融合在一起。使用ASP.NET Web應用程序項目模板添加一個新項目。將引用添加到演示文稿和數據庫項目以及system.data.entity組件。
將名為“ GridView1”的GridView和一個名為“ btnrefresh”的按鈕添加到default.aspx頁面。將代碼添加到頁面後面的代碼中。
列表4-默認值。 ASPX代碼背後
public partial class _Default : System . Web . UI . Page , IView
{
private CustomersPresenter presenter ;
protected override void OnInit ( EventArgs e )
{
presenter = new CustomersPresenter ( this ) ;
}
protected void Page_Load ( object sender , EventArgs e )
{
if ( ! IsPostBack )
{
PrepareView ( ) ;
}
}
protected void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public IList < Database . Customer > Customers
{
set
{
GridView1 . DataSource = value ;
GridView1 . DataBind ( ) ;
}
}
#endregion
}ASPX頁面所做的所有代碼都是實現Iview接口,創建主持人並將IView實現(即本身)傳遞到其構造函數中。然後,剩下的就是在適當的時間觸發iView接口的preparview()事件。
在創建主持人期間,事件處理程序將自動分配給此事件,該事件確保觸發時,主持人知道如何更新頁面維護的客戶集合。該頁面本身對這些數據的來源或如何檢索一無所知。啞巴的視圖越好。
在瀏覽器中查看此頁面時,這是結果:
圖7- ASP.NET網站演示

說明:不要忘記將實體框架要求的連接串添加到Web.config配置文件中。您可以在數據庫類庫項目的app.config文件中找到ConnectionsTring。當Visual Studio生成實體數據模型時,它會自動插入其中。
當然,示例源代碼中提供的連接字符串是針對我的本地數據庫構造的。因此,請確保相應地調整它們。
圖8-更新的解決方案

作為本文的最後一步,我們使用Windows表單應用程序創建視圖,只是為了向您展示MVP模式的真正靈活性。這樣做的步驟幾乎與創建ASP.NET網站的上一個示例相同。將新的Windows表單應用程序添加到您的解決方案中,並將引用添加到數據庫和演示項目以及System.Data.Entity Assembly中。
接下來,將DataGridView和按鈕控件添加到表單中。表格的代碼顯示在下面的列表中。它幾乎與default.aspx頁面相同。另外,不要忘記將實體框架連接字符串添加到app.config配置文件中。
清單5 -form1.cs代碼
using System ;
using System . Windows . Forms ;
using Presentation . Presenters ;
using Presentation . ViewInterfaces ;
namespace WindowsFormsApplication
{
public partial class Form1 : Form , IView
{
private CustomersPresenter presenter ;
public Form1 ( )
{
InitializeComponent ( ) ;
presenter = new CustomersPresenter ( this ) ;
}
private void Form1_Load ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
private void btnRefresh_Click ( object sender , EventArgs e )
{
PrepareView ( ) ;
}
#region IView Members
public event VoidEventHandler PrepareView ;
public System . Collections . Generic . IList < Database . Customer > Customers
{
set
{
dataGridView1 . DataSource = value ;
}
}
#endregion
}
}瞧,我們終於完成了。請注意,儘管ASP.NET和Winforms示例在代碼中幾乎相同,但在更複雜的應用程序中可能並非如此。這兩種類型的用戶界面中的交互類型大不相同,並提出了您在所有情況下都可以使用的主持人可能並不是那麼清晰。
圖9- Windows表單應用程序演示

圖10-更新的解決方案

在本文中,通過實現ASP.NET解決方案,使用並證明了MVP模式的經典解釋。自馬丁·福勒(Martin Fowler)宣布SO以來,原始的MVP模式被認為是“退休”的。該模式現在可以分為兩個營地,為:
閱讀此Microsoft模式和實踐文章以獲取更多信息。
我寫了這篇文章,以提供當時探索MVP模式的具體實現。但是,避免使用它並等待Microsoft發布ASP.NET MVC框架可能是明智的。對於那些希望模仿模型視圖控制器(MVC)框架的人,我建議您閱讀Microsoft模式和實踐的這篇文章。
註釋:MVP模式是模型視圖控制器模式(MVC)的衍生物。在撰寫本文時,Microsoft目前正忙於開發ASP.NET MVC框架。在繪製新網站項目的架構時,我建議檢查此框架。
MVP和MVC模式之間的主要區別可以指出誰負責處理用戶輸入(例如鍵盤和MOUE事件)。在MVP模式中,GUI本身負責,需要通過事件將其委派給主持人。在MVC模式中,控制器負責處理這些事件。