Pierre Greborio.NET

Talking about .NET world

Model View Presenter in ASP.NET 2.0

Martin Fowler is introducing several new enterprise patterns for his new book. My attention was captured by the Model View Presenter pattern and I immediatly tryied to apply it in a simple ASP.NET 2.0 (version 8.0.40607.85) web form with a CheckBox, a TextBox a Button and a Label. the use case is really simple, I can edit into the textbox and save what it written into the label throught the button click event. If the checkbox is checked I can't edit anymore. [note: in this basic sample I don't have a domain object].

So, my partial class (of the view) will be:

public partial class MyView_aspx
{
  MyPresenter pmod = null;

  void Page_Load(object sender, EventArgs e)
  {
    pmod = new MyPresenter(this);
  }
  void cbEditMode_CheckedChanged(object sender, EventArgs e)
  {
    pmod.SetEditMode(cbEditMode.Checked);
  }
  void BtnLoad_Click(object sender, EventArgs e)
  {
    pmod.Save();
  }
}

and the presenter will be:

public class MyPresenter
{
  Page _form;

  public MyPresenter(Page form)
  {
    _form = form;
  }

  public void SetEditMode(bool isEditMode)
  {
    TextBox txtName = (TextBox)_form.FindControl("txtName");
    txtName.ReadOnly = isEditMode;
  }

  public void Save()
  {
    TextBox txtName = (TextBox)_form.FindControl("txtName");
    Label lblName = (Label)_form.FindControl("lblName");
    lblName.Text = txtName.Text;
  }
}

The code isn't perfect because I have to resolve the form controls at each call. The optimization could be to reference the view class (MyView_aspx) into the Presenter constructor, but this isn't possible (even if the Presenter lives in the same assembly of the web application) in this build (MS folks, is it by design or it will be possible in beta 2 ?).

Comments

John Meilleur said:

I am sure you can pass around the 'MyView_aspx' class but because the form controls are protected you still can't access them. You could use 'MyPresenter' as a nested class to get around that issue which probably isn't a bad idea since they are tightly coupled anyways.
# January 6, 2005 10:43 PM

Pierre Greborio said:

Yes, an inner class could be the solution, but it isn't an ideal solution. If you have a single Presenter class for more than one View (I have several use cases available for this scenario) you can't use inner classes.

Moreover, one of the most important advantages it that you can test (unit testing) the navigation (behavior) logic easly (no screen scrapting). With inner classes and protected classes you can't.

In ASP.NET 1.1 this can be accomplished (ok, you have to change comesthing).
# January 7, 2005 2:45 AM

John Meilleur said:

If you want a single presenter to work with multiple (strongly typed) views how would you accomplish that? The only way I can think of is via the FindControl method you've outlined in your example.

Nested classes are just as easy to test as the rest of them as they can have public visibility. Aside from the fact that they can gain access to properties of their parent they still behave much like an independant class.

I'm curious how you would accomplish this in ASP.NET 1.1. Could you elaborate?
# January 7, 2005 5:51 PM

Pierre Greborio said:

Well,
consider you have a mock object that is similar to the view (ASP.NET page). It is like to have two view for one presenter.

Other cases is when you have the same objects (grids, lists, etc.) with the same behavior but with a really different layout (ie, professional layout and consumer layout).

In ASP.NET 1.x your page ia a class with protected controls. Well, if the controls fields become public the presenter class can access to them directly (without FindControl).

This isn't a serious limitation, but makes simpler to implement and test.
# January 8, 2005 2:33 PM

John Meilleur said:

This is interesting. I like the pattern and am trying to incorporate this into my code.

What I came up with is to define the view as an interface and implement that for the page. The advantage I can see here is the view can differ from the page and the page can define multiple views.

This is what it looks like...

public interface IMyView
{
TextBox TextBox { get; }
Label Label { get; }
}

public partial class MyPage1 : IMyView
{
#region IMyView Implementation
TextBox IMyView.TextBox { get { return txtFirstName; } }
Label IMyView.Label { get { return lblFirstName; } }
#endregion

void btnButton_Click(object sender, EventArgs e)
{
MyPresenter presenter = new MyPresenter(this);
presenter.Click();
}
}

public partial class MyPage2 : IMyView
{
#region IMyView Implementation
TextBox IMyView.TextBox { get { return txtLastName; } }
Label IMyView.Label { get { return lblLastName; } }
#endregion

void btnButton_Click(object sender, EventArgs e)
{
MyPresenter presenter = new MyPresenter(this);
presenter.Click();
}
}

public class MyPresenter
{
IMyView view = null;

public MyPresenter(IMyView view)
{
this.view = view;
}

public void Click()
{
view.Label.Text = view.TextBox.Text;
}
}
# January 8, 2005 7:43 PM

Pierre Greborio said:

Excellent John, that is the best best solution working for ASP.NET (all versions). I hope Orcas will support it better :-)
# January 9, 2005 2:57 AM

Matt Berther said:

I believe what the pattern is after is a complete decoupling of UI code.

Taking John's ideas a little further...

I believe this is probably more what the pattern suggests:

public interface IMyView
{
string FirstName { get; set; }
string LastName { get; set; }

string FullName { get; set; }
}

public class MyPage1 : IMyView
{
IMyView.FirstName
{
get { return txtFirstName.Text; }
set { txtFirstName.Text = value; }
}

IMyView.LastName
{
get { return txtLastName.Text; }
set { txtLastName.Text = value; }
}

IMyView.FullName
{
get { return fullNameLabel.Text = value; }
set { fullNameLabel.Text = value; }
}

void btnButton_Click(object sender, EventArgs e)
{
// this would probably be a class level declaration
// so that it is not recreated every time. In this trivial example,
// it doesnt matter
MyPresenter presenter = new MyPresenter(this);
presenter.CreateFullName();
}
}

public class MyPresenter
{
private IMyView view;

public MyPresenter(IMyView view)
{
this.view = view;
}

public void CreateFullName()
{
view.FullName = String.Format("{0} {1}", view.FirstName, view.LastName);
}
}

At this point, the Model contains all the UI widgets. Since we do not have any UI components in our View interface, this would make the view and the presenter truly portable.

# January 9, 2005 4:47 AM

John Meilleur said:

That's a good point Matt, thanks.
# January 9, 2005 6:25 PM

Pierre Greborio said:

Matt, good point.

But how to extend other kind of behavior (not only data), ie.

class Presenter {
void SetReadOnly() {
// cycle on all input controls and set them readonly.
}
}

It seems that to have the control from the Presenter class is better. The logic could more complex (not all fields are read-only)...

# January 10, 2005 12:55 PM

TrackBack said:

# March 6, 2005 11:27 AM