基於MVP架構設計ASP.Net的應用研究

來源:互聯網
上載者:User

微軟的Microsoft patterns & practices小組, Web Client Software Factory採用是Mode View Presenter+Controller模式(MVPC模式),它可以使Model-View-Presenter 模式實現商務邏輯與表現層的適當分離,高效的簡單的進行單元測試,更方便的隱藏技術的複雜度,方便的與各種系統整合等特點。以後我會單獨來的來說明Web Client Software Factory的架構應用。

傳統的ASP.NET開發設計是使用後置字碼頁面方式隔離商務邏輯。雖然大大方便了開發設計,但是ASP.NET在企業級應用開發方面還是存在如下的不足:

      後置字碼頁中混合了表現層,商務邏輯層,資料訪問層的代碼。之所以出現這種情況是因為後置代碼充當了事件引發,流程式控制制,商務規則和表現邏輯,商務邏輯和資料訪問的協調者等多種角色。後置字碼頁充當這麼多的職責導致許多難處理的代碼。在公司專屬應用程式中,一個良好的設計原則是各層之間的適當分離和保持後置字碼頁內容的儘可能乾淨。使用Model-View-Presenter 模式,後置代碼的內容將非常簡單,嚴格的管理表現層內容。

       後置代碼模型的另一個缺點是它難以不藉助協助類/工具類實現重用後置字碼頁面之間的可重用代碼。很明顯的,這也是提供了一個適當的解決方案,但往往導致ASP式的類,不像是一流的對象。通過適當的設計,每個類都應有清晰的職責。

      最後,對後置字碼頁進行單元測試非常困難因為它們同表現層的太緊密了,當然可以選擇NUnitASP這樣的工具,但是他們非常的耗費時間,並且難以維護。單元測試應當是簡單快速的。

  可以採用各種技術手段是後置字碼頁保持分離。例如Castle MonoRail項目仿效Ruby-On-Rails ,但是放棄了ASP.NET的事件模型。Maverick.NET是一個支援ASP.NET事件模型的架構但是保留後置字碼頁作為程式的控制器。理想的解決方案是使用ASP.NET的事件模型並保持後置字碼頁的儘可能簡單。Model-View-Presenter 模式是一個不需要藉助第三方架構實現這個目標。

Model-View-Presenter

Model-View-Presenter (MVP) 模式是 Model-View-Controller (MVC) 模式的增強,(MVC均指ASP.NET MVC Framework)針對事件模型,像ASP.NET這樣的架構。MVP最初使用主要的變化是Presenter實現MVC的Observer設計,基本設計和MVC相同:Model儲存資料,View表示Model的表現,Presenter協調兩者之間的通訊。在 MVP 中 View 接收到事件,然後會將它們傳遞到 Presenter, 如何具體處理這些事件,將由 Presenter 來完成。關於在 MVC 和 MVP的深入比較:

  

MVC(模型-視圖-控制器)模式是80年代Smalltalk-80出現的一種軟體設計模式,後來得到了廣泛的應用,其主要目的在於促進應用中模型,視圖,控制器間的關注的清晰分離。(模型-視圖-表示器)模式則是主要用來隔離UI、UI邏輯和商務邏輯、資料。

對於處理流程方面兩者的區別:

在MVC中,使用者的請求首先會到達Controller,由Controller從Model擷取資料,選擇合適的View,把處理結果呈現到View上;在MVP中,使用者的請求首先會到達View,View傳遞請求到特定的Presenter,Presenter從Model擷取資料後,再把處理結果通過介面傳遞到View。

 最簡單的例子

   這個例子,客戶想在頁面上顯示當前的時間(從簡單的開始容易理解)。顯示時間的ASPX頁面是“View”。Presenter負責決定現在的時間(Model),而且把Model告知 View。我們從一個單元測試開始。

[TestFixture]

public class CurrentTimePresenterTests {

    [Test]

    public void TestInitView() {

        MockCurrentTimeView view = new MockCurrentTimeView();

        CurrentTimePresenter presenter = new CurrentTimePresenter(view);

        presenter.InitView();

         Assert.IsTrue(view.CurrentTime > DateTime.MinValue);

    }

    private class MockCurrentTimeView : ICurrentTimeView {

        public DateTime CurrentTime {

            set { currentTime = value; }

            // This getter won't be required by ICurrentTimeView,

            // but it allows us to unit test its value.

            get { return currentTime; }

        }

        private DateTime currentTime = DateTime.MinValue;

    }

}

上面的單元測試代碼和右邊的類圖,描述了MVP各個元素之間的關係。單元測試中建立的第一個對象執行個體是MockCurrentTimeView,從這個單元測試中可以看出,所有的表現邏輯的單元測試並沒有一個ASPX頁面(View),所需要的是一個實現視圖介面的對象;因此可以建立一個視圖的類比對象(Mockview)代替真正的視圖對象。

下一行代碼建立了一個Presenter的對象執行個體,通過它的建構函式傳遞了一個實現ICurrentTimeView介面的對象,這樣,Presenter現在能夠操作View,從類圖中可以看出,Presenter只與View的介面通訊。這允許實現相同的View介面的多個View被Presenter使用。

最後,Presenter調用InitView()方法,這個方法將擷取當前的時間並通過公開的屬性ICurrentTimeView傳遞給視圖(View),單元測試斷言CurrentTime的值應比它的初始值大(如果需要可以做更多的斷言)。

那麼現在要做的就是要運行單位測試並通過了!

ICurrentTimeView.cs – 視圖介面

使單元測試編譯通過的第一步是建立ICurrentTimeView.cs,這個介面提供Presenter 和 View之間的溝通橋樑,在這個例子中,視圖介面需要暴露一個Model資料,使Persenter能夠將Model(目前時間)傳遞給View。

public interface ICurrentTimeView {

    DateTime CurrentTime { set; }

}

因為只需要顯示模型資料,視圖介面中只需要一個CuttentTime的Set;但是設定了一個Get,用於在單元測試中擷取視圖的CurrentTime,它也可以添加到MockCurrentTimeView而不要在介面中定義,這樣,在視圖介面中暴露的介面屬性不需要定義getter/setter(上面的單元測試就使用了這個技術)。

CurrentTimePresenter.cs - The Presenter
Presenter處理同Model之間的邏輯並將Model傳遞給View。要使單元測試通過編譯,Presenter的實現代碼如下:

public class CurrentTimePresenter {

    public CurrentTimePresenter(ICurrentTimeView view) {

        if (view == null) throw new ArgumentNullException("view may not be null");

        this.view = view;

 }

    public void InitView() {

        view.CurrentTime = DateTime.Now;

    }

    private ICurrentTimeView view;

}

完成上述代碼,我們就完成了Unit Test,mock view,Presenter和View.單元測試現在可以成功編譯並通過。下一個步驟是建立ASPX頁面充當真正的View。

注意到ArgumentNullException異常的檢查,這項技術被稱為基於契約設計(Design By Contract),在代碼中象這樣做必要的檢查可以大大的降低Bug的數量。關於基於契約設計(Design By Contract)的

ShowMeTheTime.aspx - The View

這個頁面需要做以下內容:

1、 ASPX頁面需要提供一個方法顯示當前的時間,用一個Label控制項顯示時間

2、後置代碼必須實現介面IcurrentTimeView

3、後置代碼必須建立一個Presenter對象,並把自己傳遞給它的建構函式

4、建立好Persenter對象後,需要調用InitView() 

ASPX 頁面:

<asp:Label id="lblCurrentTime" runat="server" />

...

<

ASPX 後置字碼頁面:

public partial class ShowMeTheTime : Page, ICurrentTimeView

{

    protected void Page_Load(object sender, EventArgs e) {

        CurrentTimePresenter presenter = new CurrentTimePresenter(this);

        presenter.InitView();

    }

    public DateTime CurrentTime {

        set { lblCurrentTime.Text = value.ToString(); }

    }

}

  總而言之,是的,但是很有很多的內容。上面這個例子給你的不好印象是這麼小的功能需要做那麼多的工作。我們已經從建立ASPX頁面到一個Presenter類,一個View介面和一個單元測試類……,我們獲得的好處是對Presenter的單元測試,也就是很容易的對後置字碼頁面進行單元測試。這是一個最簡單的例子就像寫“Hello World”這樣。當構建企業級應用程式的時候就會體現出MVP模式的好處。下面的主題是企業級的ASP.NET應用中使用MVP模式。

在企業級ASP.NET應用中使用MVP

1、 使用使用者控制項封裝Views:這個主題討論使用者控制項作為MVP中的View

2、MVP的事件處理:這個主題討論連同頁面驗證傳遞事件到Presenter,IsPostBack和將訊息傳遞到View

3、MVP和PageMethods的頁面重新導向:這個主題討論使用使用者控制項作為View,如何使用PageMethods處理頁面重新導向。

4、MVP的Presentation安全控制:這個主題討論如何根據基本的安全限制顯示/掩藏View中的區段

5、使用MVP的應用的架構(進階):這是個重點,這個主題展示一個使用Nhibernate作為資料訪問層的MVP應用。

使用使用者控制項封裝Views

 在上面的例子中,ASPX頁面充當View,把ASPX頁面做View只有一個簡單的目的—顯示當前的時間。但是在一個比較有代表性的應用中,一個頁面通常包含一個或者多個功能性的區段,他們可能是WebPart,使用者控制項等等。在企業級應用中,保持功能性的分離以及很容易的從一個地方移動到另一個地方是非常重要的。使用MVP,使用者控制項用於封裝View,ASPX作為 “View Initializers”和頁面的重新導向。擴充上面的例子,只要修改ASPX頁面的實現。這也是MVP的另一個好處,許多變化可以限制在View層而不要修改Presenter和Model。 

ShowMeTheTime.aspx Redux - The View Initializer

用這種新的方式,ShowMeTheTime.aspx負責下列各項:

1、ASPX上面需要聲明實現ICurrentTimeView介面的使用者控制項

2、 後置代碼必須建立一個Presenter對象,並把使用者控制項傳遞給它的建構函式

3、 建立好Persenter對象後,需要調用InitView()

ASPX 頁面:...

<%@ Register TagPrefix="mvpProject" TagName="CurrentTimeView" Src="./Views/CurrentTimeView.ascx" %>

 <mvpProject:CurrentTimeView id="currentTimeView" runat="server" />

...

 The ASPX 後置字碼頁面:

 public partial class ShowMeTheTime : Page // No longer implements ICurrentTimeView

{

    protected void Page_Load(object sender, EventArgs e) {

        InitCurrentTimeView();

    }

 private void InitCurrentTimeView() {

        CurrentTimePresenter presenter = new CurrentTimePresenter(currentTimeView);

        presenter.InitView();

    }

}

CurrentTimeView.ascx – 使用者控制項作為View

使用者控制項現在充當View,完全取決於我們所期望的View是什麼樣的

 The ASCX 頁面:...

<asp:Label id="lblCurrentTime" runat="server" />

...

ASCX 後置字碼頁面:

public partial class Views_CurrentTimeView : UserControl, ICurrentTimeView

{

    public DateTime CurrentTime {

        set { lblCurrentTime.Text = value.ToString(); }

    }

}

使用使用者控制項作為View的利弊

使用使用者控制項作為MVP的View的主要缺點是添加另一個元素的方式。現在MVP元素由以下元素組成:unit test, presenter, view interface, view implementation (the user control) 和the view initializer (the ASPX page).使用使用者控制項作為View的好處如下:

1、View非常容易的從一個頁面移到另一個頁面,這是大型的應用程式中經常發生的事

2、View在不需要複製代碼就可以在不同的頁面之間重用

3、View可以在不同的aspx頁面中進行初始化。例如一個用於顯示項目列表的使用者控制項。在網站的報表地區使用者可能看並且可以過濾資料。在網站的另一個地區使用者只能看部分資料和不能使用過濾器。在實現方面,同一個View可以傳給相同的Presenter,但是不同的Aspx頁面可以調用Presenter的不同方法初始化View

l4、添加其他View到ASPX頁面並不需要額外的代碼,只需要將使用者控制項添加到頁面,然後在後置代碼中把它和他的Presenter串連在一起就可以了。在同一頁面中沒有使用使用者控制項管理不同的功能性區段,很快就會出現維護困難的問題。

MVP的事件處理

上面的例子,本質上描述的是一個Presenter同它的View之間的單向的通訊。Presenter同Model通訊,並把它傳遞給View。大多數情況下,引發的事件需要Presenter進行處理。此外一些事件依賴於頁面上的驗證是否通過或者是IsPostBack。例如資料繫結,在IsPostBack的時候不能被引發。

聲明:Page.IsPostBack和Page.IsValid是Web特有的。下面所討論的Presenter層只在Web環境中有效。但是只要做小小的修改,也能很好工作在Webform,Winform和Mobile應用中。無論如何,他們的理論基礎都是一樣的。

簡單的事件處理順序圖表

繼續上面的例子,使用者可能要給目前時間上增加幾天,然後在View中顯示更新的時間,假設使用者輸入的是有效數字,View中顯示的時間應等於目前時間加上增加的天數。當不是IsPostBack的時候,View顯示的事目前時間,當IsPostBack的時候,Presenter應當對事件作出回應。下面的順序圖表表示了使用者的初始請求(上面部分)和使用者點擊按鈕”Add days”之後發生了什麼.。

A)建立使用者控制項

這一步只是表示ASPX頁面中聲明的使用者控制項。在頁面初始化的時候,使用者控制項被建立。在圖中表示的是實現介面IcurrentTimeView的使用者控制項。在ASPX頁面的後置代碼的Page_Load事件,Presenter建立了一個執行個體,使用者控制項作為參數通過建構函式傳遞給Presenter,到此為止,所有的描述的內容都和“使用使用者控制項封裝Views”的一樣。

B) Presenter 添加到View

為了使事件能夠從View(使用者控制項)傳遞到Presenter。View必須包含一個CurrentTimePresenter對象的引用,為了實現這個目的,View Initializer, ShowMeTheTime.aspx將Presnter傳遞給View。這不會造成Presenter和View之間的依賴,Presenter依賴於View的介面,View依賴於Presenter對事件的處理,讓我們代碼中看他們是如何工作的。

ICurrentTimeView.cs - The View Interface

public interface ICurrentTimeView {

    DateTime CurrentTime { set; }

    string Message { set; }

    void AttachPresenter(CurrentTimePresenter presenter);

}

<A name=EventHandlingPresenter>CurrentTimePresenter.cs - The

Presenter</A></H3><PRE>public class CurrentTimePresenter {

    public CurrentTimePresenter(ICurrentTimeView view) {

        if (view == null) throw new ArgumentNullException("view may not be null");

        this.view = view;

    }

    public void InitView(bool isPostBack) {

        if (! isPostBack) {

            view.CurrentTime = DateTime.Now;

        }

    }

    public void AddDays(string daysUnparsed, bool isPageValid) {

        if (isPageValid) {

            view.CurrentTime = DateTime.Now.AddDays(double.Parse(daysUnparsed));

        }

        else {

            view.Message = "Bad inputs...no updated date for you!";

        }

    }

    private ICurrentTimeView view;

}

CurrentTimeView.ascx - The View

The ASCX Page:..

<asp:Label id="lblMessage" runat="server" /><br />

<asp:Label id="lblCurrentTime" runat="server" /><br />

<br />

<asp:TextBox id="txtNumberOfDays" runat="server" />

<asp:RequiredFieldValidator ControlToValidate="txtNumberOfDays" runat="server"

    ErrorMessage="Number of days is required" ValidationGroup="AddDays" />

<asp:CompareValidator ControlToValidate="txtNumberOfDays" runat="server"

    Operator="DataTypeCheck" Type="Double" ValidationGroup="AddDays"

    ErrorMessage="Number of days must be numeric" /><br />

<br />

<asp:Button id="btnAddDays" Text="Add Days" runat="server"

    OnClick="btnAddDays_OnClick" ValidationGroup="AddDays" />

...

The ASCX Code-Behind Page:

public partial class Views_CurrentTimeView : UserControl, ICurrentTimeView {

    public void AttachPresenter(CurrentTimePresenter presenter) {

        if (presenter == null) throw new ArgumentNullException("presenter may not be null");

        this.presenter = presenter;

    }

    public string Message {

        set { lblMessage.Text = value; }

    }

    public DateTime CurrentTime {

        set { lblCurrentTime.Text = value.ToString(); }

    }

    protected void btnAddDays_OnClick(object sender, EventArgs e) {

        if (presenter == null) throw new FieldAccessException("presenter has not yet been initialized");

        presenter.AddDays(txtNumberOfDays.Text, Page.IsValid);

    }

    private CurrentTimePresenter presenter;

}

ShowMeTheTime.aspx - The View Initializer

The ASPX Page:...

<%@ Register TagPrefix="mvpProject" TagName="CurrentTimeView" Src="./Views/CurrentTimeView.ascx" %>

<mvpProject:CurrentTimeView id="currentTimeView" runat="server" />

...

The ASPX Code-Behind Page:

public partial class ShowMeTheTime : Page  // No longer implements ICurrentTimeView


        protected void Page_Load(object sender, EventArgs e) {

        InitCurrentTimeView();

    }

    private void InitCurrentTimeView() {

        CurrentTimePresenter presenter = new CurrentTimePresenter(currentTimeView);

        currentTimeView.AttachPresenter(presenter);

        presenter.InitView(Page.IsPostBack);

    }

}

C) Presenter InitView

如需求所定義的,如果不是IsPostBack,Presenter只是顯示當前的時間。Presenter要知道在IsPostBack的時候該做些什麼,這不應該由Aspx的後置代碼來決定。在上面的代碼中你看到了Aspx的後置代碼中沒有IsPostBack的處理。它只是簡單將值傳給Presenter,由Presenter來決定執行什麼樣的動作。

這可能導致一個問題:“如果是另一個使用者控制項引發的Post-back將會發生什麼呢”。在這個例子中,當前的時間會儲存在Label控制項的ViewState中而再次顯示在Label控制項上,這些都依賴客戶的需要。總體上,這是一個Presenter的好問題 –另一個使用者控制項引發的Post-back對這個使用者控制項的影響。即使你沒有使用MVP,也是一個好問題。

執行個體源碼下載:

MVP.SampleApp.rar (624.74 kb)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.