I just had a great question asked by David Woods about one scenario that often comes up when utilising the Model View Presenter pattern:
Q: How do you deal with drop down lists? They vary from web to windows and I want to refactor my app so that it can use either
A: The code for this post shows one solution to the problem. An interface (ILookupList) is created to abstract the details of the UI specific list controls from the presenter:
namespace Lists.Web.Controls.Test
{
public interface ILookupList
{
void Add(ILookupDTO dto);
void Clear();
}
}
One concrete implementation of this interface is the WebLookupList class:
using System.Web.UI.WebControls;
namespace Lists.Web.Controls.Test
{
public class WebLookupList : ILookupList
{
private readonly ListControl listControl;
public WebLookupList(ListControl listControl)
{
this.listControl = listControl;
}
public void Add(ILookupDTO dto)
{
listControl.Items.Add(new ListItem(dto.Text, dto.Value));
}
public void Clear()
{
listControl.Items.Clear();
}
}
}
Notice how all the WebLookupList does is delegate to an actual “Web” ListControl. ListControl is the base class for both DropDownList and ListBox, so it is useful to use the superclass in this case. The last piece of the puzzle is the LookupCollection class:
using System.Collections.Generic;
namespace Lists.Web.Controls.Test
{
public class LookupCollection
{
private readonly IEnumerable<ILookupDTO> dtos;
public LookupCollection(IEnumerable<ILookupDTO> dtos)
{
this.dtos = dtos;
}
public void BindTo(ILookupList list)
{
list.Clear();
foreach (ILookupDTO dto in dtos)
{
list.Add(dto);
}
}
}
}
A LookupCollection gets instantiated with an IEnumerable<ILookupDTO> and can then be told to bind itself to any ILookupList. The ILookupDTO interface is just an interface that can be implemented by objects that want to be used as values in a lookup list. With all of these pieces in place it becomes relatively trivial to populate a list on a web page with all of the customers in the system. Take a look at an interface for such a view:
namespace Lists.Web.Controls.Test
{
public interface IViewContactView
{
ILookupList Contacts { get;}
}
}
Notice that the Contacts property is readonly. A presenter that can work with this view looks as follows:
using System.Collections.Generic;
namespace Lists.Web.Controls.Test
{
public class ViewContactsPresenter
{
private IViewContactView view;
public ViewContactsPresenter(IViewContactView view)
{
this.view = view;
}
public void Initialize()
{
new LookupCollection(GetAllContacts()).BindTo(view.Contacts);
}
public IEnumerable<ILookupDTO> GetAllContacts()
{
for (int i = 0; i < 20; i++)
{
yield return new ContactDTO(i.ToString("Name 0"), i.ToString("Address 0"), i.ToString());
}
}
}
}
In this example, assume that the GetAllContacts method actually lives in a service layer. The code in the Initialize method is now taking advantage of the LookupCollection class to populate the list on the view. The resulting code-behind for a web page becomes much tighter
using System;
using System.Web.UI;
using Lists.Web.Controls.Test;
public partial class _Default : Page,IViewContactView
{
ViewContactsPresenter presenter;
protected void Page_Load(object sender, EventArgs e)
{
presenter = new ViewContactsPresenter(this);
if (! IsPostBack)
{
presenter.Initialize();
}
}
public ILookupList Contacts
{
get { return new WebLookupList(this.contactDropDown); }
}
}
Hope this brief explanation, along with the accompanying source code, gives you a place to start David!!
For those of you who are curious about the order I wrote the tests (as this was done using TDD) here it is:
- 1)WebLookupListTest.cs – ShouldAddItemToUnderlyingList
- 2)WebLookupListTest.cs – ShouldClearUnderlyingList
- 3)LookupCollectionTest.cs – ShouldBindToLookupList
Those 3 tests alone helped drive out the interfaces,classes etc. I did not write tests for the presenter and the view as MVP was not the main focus of the post, as much as how to have the presenter talk to the list controls. If you have any questions or feedback, please don’t hesitate to ask.
Resources:
