MVC Declarative Binding

One of the things that makes the bar to implement any "MVC-ish" pattern high is the tremendous amount of plumbing required just to get started in order for the different entities to communicate state changes.  This article introduces a utility library that can be used to declaratively bind the model and view encapsulating all the wiring required and making implementation much easier.

Here's how to consume the library:


The idea is to declaratively bind property and collection change events.  I have created two attributes for this: BoundToPropertyAttribute and BoundToCollectionAttribute.   We can either define these on an interface the view will implement (which is the usage I prefer) or we can declare the attributes on the concrete view.

public interface IPersonView
{
    [BoundToProperty(typeof(Person), "Name")]
    String Name { getset; }
 
    [BoundToCollection(BoundCollectionChangedMethod.AddItems)]
    void FriendsAdded(IEnumerable<Person> people);
 
    [BoundToCollection(BoundCollectionChangedMethod.RemoveItems)]
    void FriendsRemoved(IEnumerable<Person> people);
 
    [BoundToCollection(BoundCollectionChangedMethod.ResetItems)]
    void FriendsReset(IEnumerable<Person> people);
}


Then we need a model object that can broadcast its changes  by implementing INotifyPropertyChanges and/or INotifyCollectionChanged.  Here's a rough one just for demonstration purposes...

public class Person: INotifyPropertyChanged
{
    private readonly ObservableCollection<Person>
        m_Friends = new ObservableCollection<Person>();
 
    private string
        m_Name;

    public string Name
    {
        get { return m_Name; }
        set
        {
            if(m_Name == value)
                return;
            m_Name = value;
            const string
                cName = "Name";
            RaisePropertyChangedEvent(cName);
        }
    }
 

    public void AddFriend(Person p)
    {
        m_Friends.Add(p);
    }

    private void RaisePropertyChangedEvent(string cName)
    {
        if (null != PropertyChanged)
            PropertyChanged(this, new PropertyChangedEventArgs(cName));
    }
 

    public ICollection<Person> Friends
    {
        get
        {
            return new ReadOnlyObservableCollection<Person>(m_Friends);
        }
    }
 

    public event PropertyChangedEventHandler PropertyChanged;
}


Now, we have to wire the view and the model up which is usually where things get a little hairy.  I have built a cache to hold all the references to the bindings between the two objects.  I have called this a BindingCache and it will be instantiated in the controller where the reference will be kept.  The BindingCache object has methods to do the wiring up of model's change events with views that have the BindToPropertyAttribute or BindToCollectionAttributes that declare where we want receptors for the events.  All of the bindings are created when the Initialize() method is called (see below) from the controller :

public class PersonViewController
{
    public PersonViewController()
    {
        m_BindingCache = new BindingCache();
    } 

    private readonly BindingCache
        m_BindingCache;
 

    private Person
        m_Model;
 

    public void Initialize(Person model, IPersonView view)
    {
        m_Model = model;
        // wire up changes
        m_BindingCache.BindPropertyChanges(model, view);
        m_BindingCache.BindCollectionChanges(model.Friends, view);
        // send values to set initial view state
        view.FriendsReset(model.Friends);
        view.Name = model.Name;
    }
}


And that's all it takes to completely wire up the view to receive change events from the model.

The library, as it stands now, only wires up to collections that implement the INotifyCollectionChanged interface, so if we don't roll our own collection implementing this method we should use the ObservableCollection<T>.

The method signatures that receive collection change information and are decorated by BindToCollectionAttributes are pretty simple.   All the recipient methods need to have one parameter which is an IEnumerable<T> of the type the collection being listened to. The only one that could throw you for a loop at first is the receptor method for "reset" events.   When a collection is reset, the entire contents of the collection are sent to this receptor method so that the view's representation can be rebuilt.  The other two receptors receive only additional elements or removed elements.

One additional feature is that if the view (as well as the model) implement INotifyPropertyChanged  and broadcasts changes on the receptor property then the property binding becomes two-way.  In other words, changes in the view will update the model.

The idea behind this library is that we can spend more of our time coding business logic and focus less on plumbing.  When we can rely more on binding to update our object's states it allows the supervising controller pattern to really shine.

Architecture:

The core architecture of the library is very simple.  There are three core classes:

1)      PropertyBinding: Keeps track of a properties event wiring mechanisms (binds one reception method to one property change)

2)      CollectionBinding: Keeps track of a collection's event wiring mechanisms (binds one reception method to one collection change event type [add, remove, or update])

3)      BindingCache: Keeps track of the bindings

 
After that there are the attribute classes used to decorate your classes that should recieve change notifications and factories for building the bindings.  There is also a special class to handle any two-way binding opportunities that come up:


 
The only publicly exposed classes are the attributes, an enum for the collection change type and the binding cache which should make learning the API relatively easy:



So overall, it is a pretty simple solution structurally and the implementation code is less than 200 lines.   I haven't had a chance to release this library into the wild and see how it does so it should still be considered beta (as of 06/2009).  I will be making some tweaks and upgrades over time as I dog food it. The current version of the code base will live on Codeplex and if you would like to contribute or just have comments/suggestions let me know.

Hopefully you'll find this library useful.  

Until next time,
Happy Coding


Similar Articles