版本
1.1 [2007-2-12]
簡介
本教程在《NBearV3 Step by Step教程——IoC篇》的基礎上,演示如何基於NBearV3的MVP模塊實現基於NBear的IoC的MVP模式的過程。您將看到,利用封裝了NBear的IoC模塊的NBear.MVP模塊,不僅能大大加強系統表現層的可測試性,同時能充分利用NBear已有的IoC模塊獲得依賴注入能力及基於IoC的分布式服務支持。
注:在閱讀本文之前,建議讀者先閱讀《NBearV3 Step by Step教程——IoC篇》以掌握NBearV3中有關ORM和IoC的基本知識。
目標
通過本教程,讀者應能夠全面掌握使用NBearV3的MVP模塊實現表現層MVP模式。
代碼
本教程演示創建的所有工程和代碼,包含於可以從nbear.org下載的NBearV3最新源碼zip包中的tutorials\MVP_Tutorial目錄中。因此,在使用本教程的過程中如有任何疑問,可以直接參考這些代碼。
時間
<45分鐘。
正文
Step 1 下載NBearV3最新版本及准備
1.1訪問http://nbear.org,下載NBearV3的最新版本到本地目錄。
1.2 將下載的zip文件解壓至C:\,您將看到,加壓後的NBearV3目錄中包括:dist、doc、cases、src、tutorials等目錄。其中,在本教程中將會使用的是dist目錄中的所有release編譯版本的dll和exe和tutorials目錄中之前的IoC基礎教程。
1.3 將tutorials目錄中的整個IoC_Tutorial目錄復制到任意其它位置,並命名為MVP_Tutorial,我們將以IoC_Tutorial為基礎,演示NBearV3中基於IoC的分布式開發的知識。
Step 2 定義View和Presenter
2.1 將MVP_Tutorial中的IoC_Tutorial.sln重命名為MVP_Tutorial.sln,並在VS2005開發環境中打開。
2.2 我們知道MVP模式中,有Model、View和Presenter三個部分。在NBear.MVP中,Model部分,我們直接使用基於NBear.IoC的Service,因此,對於原來的IoC教程的代碼,我們只需要額外定義View和Presenter的代碼。為了充分解耦M、V、P三部分,我們將用到接口、范型和IoC技術。
2.3 為sln新增一個名叫ViewInterfaces的類庫工程。添加該工程到dist\NBear.Common.dll和Entities工程的引用。在ViewInterfaces中增加一個ISampleView.cs文件,包含如下內容:
1using System;
2using Entities;
3
4namespace ViewInterfaces
5{
6 public interface ISampleView
7 {
8 int CategoryID { get; }
9 Category[] Categories { set; }
10 Product[] ProductsInCategory { set; }
11 }
12}
2.4 為sln新增一個名叫PresenterInterfaces的類庫工程。添加該工程到dist\NBear.Common.dll、NBear.MVP.dll和Entities工程的引用。在PresenterInterfaces中增加一個ISamplePresenter.cs文件,包含如下內容:
1using System;
2using Entities;
3using NBear.MVP;
4
5namespace PresenterInterfaces
6{
7 public interface ISamplePresenter : IPresenter
8 {
9 void GetCategories();
10 void GetProductsInCategory();
11 }
12}
2.5 為sln新增一個名叫PresenterImpls的類庫工程。添加該工程到dist\ NBear.Common.dll、NBear.IoC.dll、NBear.MVP.dll、ServiceInterfaces、ViewInterfaces、PresenterInterfaces和Entities工程的引用。在PresenterImpls中增加一個SamplePresenter.cs文件,實現前面定義的ISamplePresenter,包含如下內容:
1using System;
2using System.Collections.Generic;
3using Entities;
4using ServiceInterfaces;
5using PresenterInterfaces;
6using ViewInterfaces;
7using NBear.MVP;
8
9namespace PresenterImpls
10{
11 public class SamplePresenter : Presenter<ISampleView, ICategoryService>, ISamplePresenter
12 {
13 ISamplePresenter Members#region ISamplePresenter Members
14
15 public void GetCategories()
16 {
17 //in presenter we can to additional data filtering, so that services can be reused more.
18 List<Category> categoriesWithProducts = new List<Category>();
19 foreach (Category item in model.GetAllCategories())
20 {
21 if (item.Products.Count > 0)
22 {
23 categoriesWithProducts.Add(item);
24 }
25 }
26 view.Categories = categoriesWithProducts.ToArray();
27
28 if (categoriesWithProducts.Count > 0)
29 {
30 view.ProductsInCategory = model.GetCategoryByID(categoriesWithProducts[0].CategoryID).Products.ToArray();
31 }
32 }
33
34 public void GetProductsInCategory()
35 {
36 view.ProductsInCategory = model.GetCategoryByID(view.CategoryID).Products.ToArray();
37 }
38
39 #endregion
40 }
41}
2.6
至此,需要的View接口、Presenter接口和實現都定義完了。對PresenterImpls,可以和ServiceImpls一樣進行獨立的測試。這是MVP模式最大的好處。注意,PresenterImpls中SamplePresenter繼承自NBear.MVP中定義的Presenter基類,並實現IPresenter接口。該接口和基類為Presenter提供了對NBear.IoC的封裝,在繼承類中,可以訪問Presenter基類中定義的view和model這兩個protected的成員變量,分別訪問關聯的view和model。下面,我們將修改website以使用這些類。您將看到NBear.MVP通過 NBear.IoC獲得的依賴注入能力。
Step 3 在website中使用View和Presenter
3.1 在website工程中,先刪除原來的Default.aspx和關聯的Default.aspx.cs中的代碼,並添加website到ViewInterfaces、PresenterInterfaces和PresenterImpls(其實無需添加對PresenterImpls的引用,而只需要將PresenterImpls.dll復制到website的bin目錄,這裡為了省區手動復制的過程才增加了它的引用)工程的引用。我們為Default.aspx增加一個DropDownList、一個Button和一個GridView控件如下:
1<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
2
3<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4
5<html xmlns="http://www.w3.org/1999/xhtml" >
6<head runat="server">
7 <title>Untitled Page</title>
8</head>
9<body>
10 <form id="form1" runat="server">
11 <div>
12 Choose a Category: <asp:DropDownList ID="listCategories" runat="server" DataTextField="CategoryName"
13 DataValueField="CategoryID">
14 </asp:DropDownList>
15 <asp:Button ID="btnLoad" runat="server" OnClick="btnLoad_Click" Text="Load Products in Selected Cateogry" /><br />
16 <br />
17 <asp:GridView ID="gridProducts" runat="server">
18 </asp:GridView>
19 </div>
20 </form>
21</body>
22</html>
3.2
在Default.aspx.cs中,我們需要實現前面定義的ISampleView接口,並使用我們已經定義的SamplePresenter。和使用NBear.IoC 中的ServiceFactory類似,我們可以非常簡單的使用NBear.MVP中定義的PresenterFactory類,通過Presenter接口得到其實現。代碼如下:
1using System;
2using System.Data;
3using System.Configuration;
4using System.Web;
5using System.Web.Security;
6using System.Web.UI;
7using System.Web.UI.WebControls;
8using System.Web.UI.WebControls.WebParts;
9using System.Web.UI.HtmlControls;
10
11using Entities;
12using ViewInterfaces;
13using PresenterInterfaces;
14using NBear.MVP;
15
16public partial class _Default : System.Web.UI.Page, ISampleView
17{
18 private ISamplePresenter presenter;
19
20 protected void Page_Load(object sender, EventArgs e)
21 {
22 presenter = PresenterFactory.Create().GetPresenter<ISamplePresenter>(this);
23 if (!IsPostBack)
24 {
25 presenter.GetCategories();
26 DataBind();
27 }
28 }
29
30 protected void btnLoad_Click(object sender, EventArgs e)
31 {
32 presenter.GetProductsInCategory();
33 gridProducts.DataBind();
34 }
35
36 ISampleView Members#region ISampleView Members
37
38 public int CategoryID
39 {
40 get
41 {
42 if (listCategories.SelectedIndex < 0)
43 {
44 return -1;
45 }
46 else
47 {
48 return int.Parse(listCategories.Items[listCategories.SelectedIndex].Value);
49 }
50 }
51 }
52
53 public Category[] Categories
54 {
55 set
56 {
57 listCategories.DataSource = value;
58 }
59 }
60
61 public Product[] ProductsInCategory
62 {
63 set
64 {
65 gridProducts.DataSource = value;
66 }
67 }
68
69 #endregion
70}
通過PresenterFactory類的Create()我們就能獲得一個PresenterFactory類的singleton實例。通過GetPresenter()方法,傳入Presenter的接口作為范型參數,頁面自己this作為實現了ISampleView接口的實例的唯一的參數,就能得到需要的Presenter的實現類實例,這個內部的過程是通過NBear.IoC的ServiceFactory實現的,因此,可以和IoC_Adv教程中一樣使用基於分布式IoC的Service作為Model。
我們可以看到,website僅依賴於PresenterInterfaces,Prensenter的具體實現通過IoC以依賴注入方式獲得。這樣,我們可以方便地僅修改配置文件(無需重新編譯)就改變Presenter接口對應的具體的Presenter實現類。
特別注意,我們在使用SamplePresenter時完全沒有指定Model的位置,那麼SamplePresenter怎麼知道哪個model對應當前的view呢?他會通過IPresenter接口(所有的Presenter需要實現該接口)的TypeOfModel屬性返回的type,通過NBear.IoC.Service.ServiceFactory.GetService<type>()從IoC容器中自動獲得的。也因此,它自動獲得了分布式能力。
3.3 在Web.config中的castle配置節中,因為我們的程序只需要用到category service,我們可以將product service刪掉。但是,我們要在castle配置節中增加ISamplePresenter和SamplePresenter的配置。修改後的Web.config代碼如下:
1<?xml version="1.0"?>
2<configuration>
3 <configSections>
4 <section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common"/>
5 <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
6 </configSections>
7 <entityConfig>
8 <includes>
9 <add key="Sample Entity Config" value="~/EntityConfig.xml"/>
10 </includes>
11 </entityConfig>
12 <castle>
13 <components>
14 <!--You can use standard castle component decleration schema to define service interface impls here-->
15 <component id="category service" service="ServiceInterfaces.ICategoryService, ServiceInterfaces" type="ServiceImpls.CategoryService, ServiceImpls"/>
16 <component id="sample presenter" service="PresenterInterfaces.ISamplePresenter, PresenterInterfaces" type="PresenterImpls.SamplePresenter, PresenterImpls"/>
17 </components>
18 </castle>
19 <appSettings/>
20 <connectionStrings>
21 <add name="Northwind" connectionString="Server=(local);Database=Northwind;Uid=sa;Pwd=sa" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
22 </connectionStrings>
23 <system.web>
24 <compilation debug="true">
25 <assemblies>
26 <add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
27 <add assembly="System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
28 <add assembly="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies></compilation>
29 <authentication mode="Windows"/>
30 </system.web>
31</configuration>
3.4
運行website並浏覽Default.aspx頁面,我們就可以看到我們實現的功能了。改變Category的選擇,點擊按鈕就能查看不同的Category下的Products。
後記
如果看過其他MVP模式的實現,讀者可能會注意到某些區別。在這裡的實現中,Presenter不需要知道何時綁定數據,不需要處理事件回調,而只需要負責對view和model進行數據傳遞、驗證和過濾。何時綁定,以及哪些數據在IsPostBack時需要重新載入,哪些數據只需要在頁面初次載入時載入都是由Default頁面自己控制的。這樣做的好處是,Presenter對具體的表現層(這裡是可以PostBack的頁面)沒有任何概念上的依賴,做到了真正的解耦。即使要將該Presenter和Model應用到WindowsForm或者WPF表現層,也是輕而易舉的。
V1.1新增 –Presenter何如使用到多個Model?
上面的示例中的Presenter是比較典型的一個View對應Model(或者說Service)的情況。在實際的開發中,經常會出現一個Presenter需要訪問到多個Model的情況。
此時,可以有兩種可選的做法:
1、 具體的Presenter繼承含多個Model泛型參數的Presenter基類,如:Presenter<IView, IModel1, IModel2>, Presenter<IView, IModel1, IModel2, IModel3>等。使用和配置Presenter的方法和前文討論的方法完全一致。
2、 具體的Presenter繼承不含Model的Presenter<IView>基類。在Presenter具體實現類中,當需要使用任意Model(或者說Service時),只需要使用NBear.IoC.Service.ServiceFactory.Create().GetService<IServiceType>()方法就能獲得需要的Service的實例,然後,就能方便的調用service的方法。很明顯,使用這種方法相比方法1更靈活。因此,也是推薦的做法。
//正文結束