After reading Martin
Fowler’s Model View Presenter article, I did some deep thinking about how to
implement that pattern. The purpose of the pattern is to decouple UI process
logic from the UI container. If you think about the code-behind classes in
your UI, all the examples we see have button-click handlers performing process
logic. Below is my first attempt at creating a behavior layer for UI
testability and portability:
I’ll present this example using a simple ASP.NET page:
<%@ Page language=”c#” Codebehind=”default.aspx.cs”
AutoEventWireup=”false” Inherits=”ModelViewPresenter.WebForm1″
%>
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”
>
<HTML>
<body>
<form id=”Form1″ method=”post”
runat=”server”>
<asp:textbox id=”txtName”
runat=”server”>
</asp:textbox><asp:button id=”btnAdd”
runat=”server” Text=”Add Name”></asp:button>
Number of
items:
<asp:label id=”lblCount”
Runat=”server”></asp:label><br>
<asp:datagrid
id=”dgdNames”
runat=”server”>
<Columns>
<asp:ButtonColumn
CommandName=”delete” ButtonType=”PushButton” Text=”Remove”
/>
</Columns>
</asp:datagrid></form>
</body>
</HTML>
This page is used to maintain a list of names. Type in a name and click the
add button; remove a name from the list. The current names are shown in a
datagrid, and the count is shown next to the button. Here is the
code-behind:
Presenter.INamesView
15 {
16 protected
System.Web.UI.WebControls.TextBox txtName;
17 protected
System.Web.UI.WebControls.Button btnAdd;
18 protected
System.Web.UI.WebControls.DataGrid dgdNames;
19 protected
System.Web.UI.WebControls.Label lblCount;
20
21
private Presenter.NamesPresenter
_presenter;
22
23 private
Model.Names _names {
24 get { return
(Model.Names)ViewState[“names”]; }
25
set { ViewState[“names”] = value; }
26 }
27
28
private void
Page_Load(object sender, System.EventArgs
e)
29 {
30 _presenter = new Presenter.NamesPresenter(this);
31
if(!IsPostBack) {
32 _presenter.Load();
33 }
34 }
35
36
public Model.Names Names {
37 get {
return _names; }
38 set {
_names = value; }
39 }
40
41
public void
Update() {
42 dgdNames.DataSource
= _names;
43
dgdNames.DataBind();
44
45 lblCount.Text =
_names.Count.ToString();
46
}
47
48 private
void btnAdd_Click(object sender, System.EventArgs e) {
49
_presenter.AddName(txtName.Text);
50
txtName.Text = string.Empty;
51 }
52
53
private void
dgdNames_DeleteCommand(object source,
DataGridCommandEventArgs e) {
54
_presenter.RemoveName(e.Item.ItemIndex);
55 }
I have omitted the using statements and the IDE-generated code, so this is
not complete, but it shows how I am delegating behavior to the presenter layer.
Here is my presenter:
6 {
7
private INamesView _view;
8
9
public NamesPresenter(INamesView view)
{
10 _view = view;
11 }
12
13
public void
AddName(string nameToAdd) {
14 _view.Names.Add(nameToAdd);
15 _view.Update();
16 }
17
18
public void
RemoveName(int indexOfNameToRemove) {
19
_view.Names.RemoveAt(indexOfNameToRemove);
20 _view.Update();
21 }
22
23
public void
Load() {
24 _view.Names = new Model.Names();
25 _view.Update();
26 }
27 }
This presenter controls the model (my business object) and tells the UI what
to do and when through the interface that the UI must implement (below):
6 {
7
Model.Names Names{get;set;}
8
void Update();
9 }
As long as my UI implements this interface, my presenter can control it, and
I can make changes to the UI (including total replacement) without impacting the
core behavior.
Next, all this exists to manage my model or business object:
7 public class Names : IEnumerable
8 {
9
private ArrayList _entries = new ArrayList();
10
11
public string
this[int
index] {
12 get { return
_entries[index].ToString(); }
13
set { _entries[index] = value; }
14 }
15
16
public int
Count {
17 get { return
_entries.Count; }
18 }
19
20
public void
Add(string name) {
21 _entries.Add(name);
22 }
23
24
public void
RemoveAt(int index) {
25 _entries.RemoveAt(index);
26 }
27
28
public IEnumerator GetEnumerator() {
29 return
_entries.GetEnumerator();
30
}
31 }
Events from the UI are delegated to the presenter, which decides what to do
and calls interface methods on the UI. The presenter controls interaction
between the UI and the model just as a repository layer would control
presistence between the model and a data store.
I don’t pretend that this example is perfect, and I’m open for criticism, but
the model view presenter pattern for the UI is very tempting for testability and
UI portability.