At Open Force Europe, this fall, I will be presenting a talk on Creating Testable Modules. This is part 3 of a series of blogs where I intent to describe the process as I work my way through developing this talk. In previous posts, I have introduced some of the concepts including the Model View presenter design pattern. In this blog I will dig deeper into this pattern by creating a simple Hello World application. I will be using the Passive View variant of the pattern (see Figure 1).
This blog is cross-posted from my personal blog.
Figure 1 – MVP – Passive View
As can be seen from the diagram, while everything is centered on the Presenter, it is the View that receives the initial request, and it is the View that is instantiated by the WebForms Framework.
Hello World Requirements
Our “Hello World” application is actually a little more than the typical Hello World application. It has the following requirements.
- The application will include a label, a text box to enter text and two buttons – Submit and Reset.
- On initially browsing to the page the label will give the user instructions
- When the user clicks the Submit button, the label is updated to display “Hello “ and the text the user enters in the text box
- When the user clicks the Reset button, the label is reset to display the initial instructions.
These are pretty straightforward requirements, but they should allow us to see how the MVP pattern works.
Figure 2 – The Hello World MVP Web Application Project
You will note that I have followed the convention used in ASP.NET MVC, by creating separate folders for the Models, Presenters and Views. This is not required, but is just a convenience.
This particular example is not a DotNetNuke module as it is just designed to demonstrate the basic pattern.
You will also note that there is an interface for both the View (IView) and the Model (IModel). While you will almost always see interfaces defined for the View, you will not always see an interface for the Model, but interfaces make the whole system more testable as it allows us to use Mock (fake) versions of the objects when Unit Testing.
The Model
We will start by looking at the Model. Listing 1 shows the IModel Interface. Its pretty basic, one property - Text - to hold the text to display.
Listing 1 – IModel Interface
|
1: interface IModel
2: {
3: string Text { get; set; }
4: }
|
Listing 2 shows the Model Class. Again, pretty basic, the single property – Text – backed by a private member text.
Listing 2 – Model Class
|
1: public class Model : IModel
2: {
3: private string text;
4:
5: public string Text
6: {
7: get { return text; }
8: set { text = value; }
9: }
10: }
|
In fact, in order to meet our requirements we don’t actually need a Model class as we don’t need to persist the text entered by the user.
The View
We are using the Passive View variant of the MVP pattern, so our View is pretty dumb. Listing 3 shows the IView Interface.
Listing 3 – IView Interface
|
1: public interface IView
2: {
3: string Label { set; }
4: string Text { get; }
5: }
|
The interface defines the basic elements of the View that the Presenter will need to interact with – ie the Presenter will need to read the Text property and set the Label property. In addition to implementing this simple interface, the actual HelloWorldView concrete class will need to manage the events raised by the Submit and Reset buttons.
Lets first look at the ascx file for the concrete View (Listing 4). The ascx file is fairly straightforward. It has a Label, a TextBox and two Buttons as specified in the requirements.
Listing 4 – HelloWorldView.ascx
|
1: <h1>Hello World Exampleh1>
2: <h2><asp:Label ID="lbl" runat="server" />h2>
3: <asp:TextBox ID="txt" runat="server" style="width:400px" />
4: <br /><br />
5: <asp:Button ID="submit" runat="server" Text="Submit" OnClick="submit_Click" />
6: <asp:Button ID="reset" runat="server" Text="Reset" OnClick="reset_Click" />
|
In the code behind file for the concrete View (Listing 5), we see that the user control inherits from the UserControl class and implements the IView interface.
Listing 5 – HelloWorldView.ascx.cs
|
1: public partial class HelloWorldView : System.Web.UI.UserControl, IView
2: {
3: protected Presenter presenter;
4:
5: public HelloWorldView()
6: {
7: //Instantiate associated Presenter
8: presenter = new Presenter(this);
9: }
10:
11: public string Label
12: {
13: set { lbl.Text = value; }
14: }
15:
16: public string Text
17: {
18: get { return txt.Text; }
19: }
20:
21: protected override void OnLoad(EventArgs e)
22: {
23: //Call base class to ensure events are fired
24: base.OnLoad(e);
25:
26: //Run the OnViewLoaded method of the Presenter
27: presenter.OnViewLoaded(Page.IsPostBack);
28: }
29:
30: protected void submit_Click(object sender, EventArgs e)
31: {
32: presenter.Update();
33: }
34:
35: protected void reset_Click(object sender, EventArgs e)
36: {
37: presenter.Reset();
38: }
39: }
|
The two properties, Text and Label, specified in the interface encapsulate the Text properties of the Textbox and Label controls. The control has a private variable of type Presenter, which is instantiated in the control’s constructor, passing itself as a constructor parameter so the Presenter can reference the View’s public properties. The View’s event handlers (submit_Click and reset_Click) and lifecycle methods (OnLoad) call methods on this Presenter instance.
The Presenter
The Presenter is the hub of this pattern, passing information back and forth between the View and the Model. It contains private references to both the View and the Model (defined as being of types IView and IModel). As was seen above, the actual View is passed to the constructor of the Presenter, when it is instantiated in the Constructor of the View. In order to satisfy the initiial requirements the Model variable is also instantiated in the Presenters constructor and its value is set to the introduction text (IntroText).
Listing 6 – The Presenter
|
1: public class Presenter
2: {
3: private IView view;
4: private IModel model;
5:
6: private string IntroText = "Enter your name and click the Submit Button";
7:
8: public Presenter(IView view)
9: {
10: //set private view variable
11: this.view = view;
12:
13: //Instantiate Model
14: model = new Model();
15: model.Text = IntroText;
16: }
17:
18: private void DisplayText()
19: {
20: //Update View
21: if (model.Text == IntroText)
22: view.Label = model.Text;
23: else
24: view.Label = String.Concat("Hello - ", model.Text);
25: }
26:
27: public void OnViewLoaded(bool isPostBack)
28: {
29: if (!isPostBack)
30: DisplayText();
31: }
32:
33: public void Reset()
34: {
35: //Reset Model
36: model.Text = IntroText;
37:
38: //Update View
39: DisplayText();
40: }
41:
42: public void Update()
43: {
44: //Update Model
45: model.Text = view.Text;
46:
47: //Update View
48: DisplayText();
49: }
50: }
|
The OnViewLoaded, Reset and Update methods which are called by the Views lifecycle methods and event handlers, handle all the communication between the Model and the View and vice versa.
As was mentioned above the Model is not really necessary in this simple example – it is just used to store the value of the text entered by the user, and in this scenario, this could be handled by a private variable in the Presenter class itself.
This was a simple demonstration of the MVP – Passive View pattern. In the next blog I will develop some tests for it.