Mike Nichols - Son of Nun Technology

Visitor Pattern For DataBinding ListControls with Model View Presenter

Implementing the Model-View-Presenter has been fun. I appreciate it's tendency to enforce discipline in keeping any business decisions out of the View layer. I was always bewildered when I would read that the business logic should be separated from the 'code-behind' source and then see millions of DataSet examples where that was tons of business logic embedded in Web Pages.

However, trying to separate the presentation logic away and keep the client as dumb as possible presents (forgive the pun) some new challenges. Keeping the presenter interacting with only the interfaces of both the IView and IFacade (or IService or whatever...IModel) is straightforward so long as basic property setters and getters on the view are the requirements. But when you get into nested collections within collections AND want to keep the client dumb, then it is time to reach into the patterns hat for a solution.

Specifically, I wasn't sure how to have the Presenter authoritative for logic decisions while still keeping the DataBind() calls in place on my DataSource controls (like a Repeater). My first reaction is to create a kind of 'placeholder' property for the DataItem and have that iteratively set during binding for processing in the Presenter layer. For example, perhaps a different list in a dropdown list needs to appear BASED ON WHAT THE DATA ITEM IS. I want my presenter to inject the correct list, but how to do that during binding?

A refactoring has emerged that I'll call Replace Placeholder Property With Visitor. The Visitor pattern is well-suited to exactly this kind of databinding dilemma. No doubt, some will think it's heavyhanded, but I have found it elegant and keeps responsibilities between the view and presenter where they belong.

To start, say we have Person we want to edit that has a collection of Addresses. We want to bind a Repeater control on a collection of Labels so that for each label, we have a blank Address form. Within each address form, we have a drop down list of StreetTypes (Rd, St, Ln, etc). So it looks like this.

To use the Visitor pattern to inject the list of StreetTypes into the DropDown list, we'll abstract away the contracts and have the actors only deal with those. First, the ISaveAddressViewItem interface This will live in the Presentation project so the Presenter will interact with any implementation of it. Before creating that let's have it inherit from a more generic interface (explain later):

    public interface IViewRepeaterItem

    {

    }

Ok, now let's create the contract that has the property names we expect.

    public interface ISaveAddressViewItem:IViewRepeaterItem

    {

        string Label { get;}

        string SuiteApartment { get;set;}

        string PostalCode { get;set;}

        string StreetField1Value { get;set;}

        string StreetField1StreetType { get;set;}

        string StreetField2Value { get;set;}

        string StreetField3Value { get;set;}

 

        IList<StreetTypes> StreetTypes { set;}

 

 

    }

Next, let's create a custom RepeaterItem that will implement this interface. We'll put this in its own project where we create our other custom controls (NOT App_Code!):

    public class SaveAddressViewRepeaterItem : RepeaterItem, ISaveAddressViewItem,IViewRepeaterItem

    {

        public SaveAddressViewRepeaterItem(int itemIndex, ListItemType itemType) : base(itemIndex, itemType)

        {

        }

 

        public string Label

        {

            get { return ControlUtil.FindFirstControlById<System.Web.UI.WebControls.Label>(this, "Label").Text; }

        }

 

        public string SuiteApartment

        {

            get { return ControlUtil.FindFirstControlById<TextBox>(this, "SuiteApartment").Text; }

            set { ControlUtil.FindFirstControlById<TextBox>(this, "SuiteApartment").Text = value; }

        }

 

        public string PostalCode

        {

            get { return ControlUtil.FindFirstControlById<TextBox>(this, "PostalCode").Text; }

            set { ControlUtil.FindFirstControlById<TextBox>(this, "PostalCode").Text = value; }

        }

 

        public string StreetField1Value

        {

            get {return ControlUtil.FindFirstControlById<TextBox>(this,"StreetField1Value").Text; }

            set {ControlUtil.FindFirstControlById<TextBox>(this,"StreetField1Value").Text = value; }

        }

 

        public string StreetField1StreetType

        {

            get { return ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes").SelectedValue; }

            set {

                ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes").SelectedValue = value;}

        }

 

        public string StreetField2Value

        {

            get { return ControlUtil.FindFirstControlById<TextBox>(this, "StreetField2Value").Text; }

            set { ControlUtil.FindFirstControlById<TextBox>(this, "StreetField2Value").Text = value; }

        }

 

        public string StreetField3Value

        {

            get { return ControlUtil.FindFirstControlById<TextBox>(this, "StreetField3Value").Text; }

            set { ControlUtil.FindFirstControlById<TextBox>(this, "StreetField3Value").Text = value; }

        }

 

        public IList<StreetTypes> StreetTypes

        {

            set

            {

                DropDownList list = ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes");

                if (list != null)

                    list.DataSource = value;

            }

        }

 

 

    }

Before we look at the Repeater that will consume this, let's look at the contract for the Presenter that the ISaveAddressViewItem's parent will interact with during databinding

    public interface IPresenterVisitor

    {

        void PostVisit(IViewRepeaterItem item);

        void SetItemVisit(IViewRepeaterItem item);

        void LoadItemVisit(IViewRepeaterItem item);

    }

Now let's look at the Repeater control (also in the Class project, not in the Web folder) we create to bring the IViewRepeaterItem and IPresenterVisitor together. We need to tell this somewhat generic repeater control what kind of custom RepeaterItem to instantiate in order to get our implementation, so quick and dirty I add a ItemType property that I can declare in the aspx form. We also need to tell the Repeater control who the Presenter is that implements IPresenterVisitor. Finally, we need to override certain events to wireup our conversation between the View and Presenter during binding. This is where the visitor pattern shines.

    public class ViewRepeater : Repeater

    {

        public ViewRepeater()

        {

            ItemDataBound += new RepeaterItemEventHandler(SetItem);

        }

 

        private Type _itemType;

        [Bindable(true)]

        public Type ItemType

        {

            get { return (Type)ViewState["ItemType"]; }

            set

            {

                if(value.GetInterface(typeof(IViewRepeaterItem).ToString())==null)

                {

                    throw new InvalidCastException("The RepeaterItem must implement the IViewRepeaterItem interface");

                }

                ViewState["ItemType"] = value;

            }

        }

 

        private IPresenterVisitor _visitor;

 

        public IPresenterVisitor Visitor

        {

            get { return _visitor; }

            set { _visitor = value; }

        }

 

 

        protected override RepeaterItem CreateItem(int itemIndex, ListItemType itemType)

        {

            IViewRepeaterItem item = Activator.CreateInstance(ItemType, itemIndex, itemType)