DataModel-View-ViewModel pattern: 2

In part 1, I gave an overview of a pattern we use in the UI development of Max. In this post, I plan to talk about DataModels. In part 1, I wrote:

DataModel

DataModel is responsible for exposing data in a way that is easily consumable by WPF. All of its public APIs must be called on the UI thread only. It must implement INotifyPropertyChanged and/or INotifyCollectionChanged as appropriate. When data is expensive to fetch, it abstracts away the expensive operations, never blocking the UI thread (that is evil!). It also keeps the data "live". These sorts of classes are fairly straightforward to unit test.

I want to talk a bit about the threading model. Making all public APIs single threaded on a DataModel may seem like overkill. It's certainly possible to make some methods thread safe. And, you might know that WPF data binding will actually handle property changed events from other threads. But, in my experience, making the APIs of DataModel single thread significantly simplifies the models and eliminates many possible bugs. The rules become very simple. Of course, DataModels usually need to do operations on background threads, but they can use the Dispatcher to dispatch the results back to the UI thread.

Let's go through a possible DataModel base class. I will use it in the sample moving forward.

First, the defition and constructor:

    public class DataModel : INotifyPropertyChanged

    {

        public DataModel()

        {

            _dispatcher = Dispatcher.CurrentDispatcher;

        }

We grab the current Dispatcher in the constructor. We now have it available for any background operations that need to be dispatch results to the UI thread.

Now, here's the definition of the possible states of the model:

        public enum ModelState

        {

            Fectching,  // The model is fetching data

            Invalid,    // The model is in an invalid state

            Active      // The model has fetched its data

        }

I think these are pretty self explanatory. Basically, the model will be in a fetching state if it's fetching data asynchronously. Otherwise, it will be in the invalid or activate state. The state is made available through the following property:

        public ModelState State

        {

            get

            {

                VerifyCalledOnUIThread();

                return _state;

            }

 

            set

            {

                VerifyCalledOnUIThread();

                if (value != _state)

                {

                    _state = value;

                    SendPropertyChanged("State");

                }

            }

        }

This is the basic pattern we'll use for most model properties. When getting the value, we verify that we're on the UI thread and just return the cached value. When setting the property, we also verify that we're on the UI thread. And, if the value changed, we send the event that it changed. Let me fill in a couple of the utility functions now. The first is VerifyCalledOnUIThread():

        [Conditional("Debug")]

        protected void VerifyCalledOnUIThread()

        {

            Debug.Assert(Dispatcher.CurrentDispatcher == this.Dispatcher,

                "Call must be made on UI thread.");

        }

Basically, we make sure that we're on the right thread by checking the current dispatcher. The Conditional attribute makes it so this code isn't executed in retail bits. Sprinkling asserts in the code like this makes it easy to catch these violations early. Otherwise, you might end up tracing down hard to reproduce race conditions.

Next, here's our NotifyPropertyChanged event. We define our own add/remove handlers so we can verify that things are called on the UI thread. If handlers were added/removed from another thread, we'd run into threading issues.

        public event PropertyChangedEventHandler PropertyChanged

        {

            add

            {

                VerifyCalledOnUIThread();

                _propertyChangedEvent += value;

            }

            remove

            {

                VerifyCalledOnUIThread();

                _propertyChangedEvent -= value;

            }

        }

And, SendPropertyChanged is a helper function that notifies listeners that the named property changed:

        protected void SendPropertyChanged(string propertyName)

        {

            VerifyCalledOnUIThread();

            if (_propertyChangedEvent != null)

            {

                _propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));

            }

        }

That just leaves the fields:

        private ModelState _state;

        private Dispatcher _dispatcher;

        private PropertyChangedEventHandler _propertyChangedEvent;

    }

I'll be using this base class in my sample moving forward. If anyone's interested, I could make the source code available, but it's all here and I plan to make the full version with comments available when I get further along in the sample.

This class is missing one major feature that I plan to talk more about later. But, I think I need a disclaimer here in case anyone starts any of their own code based on this. As mentioned above, one of the roles of a DataModel is to keep its data "live". This can lead to memory leaks. To keep data live, the model will need to register for change notifications from some other source, or maybe set up a timer. This will keep the DataModel object alive. If there's a DataTemplate pointing to the DataModel with data binding set up, it will have registered for property changed notifications from the DataModel, so the DataModel will be keeping the UI in the DataTemplate alive, even after it's been unloaded from the UI. The solution we use is to have a reference counted activate/deactivate pattern in our DataModel classes where a model is only live while active. We activate the model when the UI pointing to it is Loaded, and deactivate when Unloaded. I'll blog about this more in the future...