다음을 통해 공유


DM-V-VM part 3: A sample DataModel

In part 2, I showed a base class for DataModels. In this post, I will describe a sample implementation. For this sample, I'll use a DataModel that represents a stock. The model will be in charge of asynchronously fetching the stock quote and making it's value available.

As part of making this class more testable, we first define an interface for retrieving stock quotes:

    public interface IStockQuoteProvider

    {

        /// <summary>

        /// Get a quote. This function may block on slow operations like hitting the network.

        /// </summary>

        /// <param name="symbol">The stock symbol.</param>

        /// <param name="quote">The quote.</param>

        /// <returns>Whether we were able to get a quote.</returns>

        bool TryGetQuote(string symbol, out double quote);

    }

This will let us plug in mock providers for unit testing. Let's walk through the model implementation. First the class declaration and constructor:

    public class StockModel : DataModel

    {

        public StockModel(string symbol, IStockQuoteProvider quoteProvider)

        {

            _symbol = symbol;

            _quoteProvider = quoteProvider;

 

            this.State = ModelState.Fectching;

 

            // Queue a work item to fetch the quote

            if (!ThreadPool.QueueUserWorkItem(new WaitCallback(FetchQuoteCallback)))

            {

                this.State = ModelState.Invalid;

            }

        }

The constructor takes the stock symbol and the quote provider. It sets the initial model state to fetching and queues a work item to be called on a background thread. Don't forget to check the return result of QueueUserWorkItem! If it fails, the model will be put in the invalid state.

Next, the symbol property. This one is simple!

        public string Symbol

        {

            get { return _symbol; }

        }

For the quote property, we'll have a private setter that will send the property changed event:

        public double Quote

        {

            get

            {

                VerifyCalledOnUIThread();

 

                return _quote;

            }

 

            private set

            {

                VerifyCalledOnUIThread();

 

                if (_quote != value)

                {

                    _quote = value;

                    SendPropertyChanged("Quote");

                }

            }

        }

Now, here's the callback to fetch the quote. Remember, this will be called on a background thread where it's okay for us to do expensive operations like hitting a web service to get a quote.

        private void FetchQuoteCallback(object state)

        {

            double fetchedQuote;

            if (_quoteProvider.TryGetQuote(_symbol, out fetchedQuote))

            {

                this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,

                    new ThreadStart(delegate

                    {

                        this.Quote = fetchedQuote;

                        this.State = ModelState.Active;

                    }));

            }

            else

            {

                this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,

                    new ThreadStart(delegate

                    { this.State = ModelState.Invalid; }));

            }

        }

We need to dispatch back to the UI thread to set the properties. This avoids any complex locking logic and makes sure that all of the property changed events come from the UI thread.

Finally, we just have the fields:

        private string _symbol;

        private double _quote;

        private IStockQuoteProvider _quoteProvider;

    }

So, that's it! What we've got is a class that's perfectly suited to being displayed in a WPF DataTemplate. It's got the symbol and quote available for binding. And, the state can be used in a trigger to change the look while it's fetching or if there's an error. The UI thread will never be blocked on slow operations.

Next up, I'll show some unit tests for this class, which will show you how to unit test code that uses a dispatcher.

Comments

  • Anonymous
    July 26, 2006
    In part 3, I showed code for StockModel, a DataModel for stocks. On the Max team, we are big believers...

  • Anonymous
    August 06, 2006
    The comment has been removed

  • Anonymous
    August 07, 2006
    Good feedback. It would probably have been better to also show a factored out StockQuote class that just contains the raw data. I think that's a more typical pattern. The quote provider can have the logic to batch up queries, but it really hasn't been given enough information to do so properly in this case.

    The big advantage to having the model control when to retrieve the data is that it helps with virtualization. However, the way I wrote it in this example, you don't get that advantage. Hold on and I'll show a more realistic example where we only fetch the expensive data when the DataModel is visible in the UI. This is a key piece of the pattern that I left out.

  • Anonymous
    September 01, 2006
    I've noticed that in the sample here you retreive the stock information inside a thread.... What happens with the following scenario:

    You need to fetch a photo prom the internet (let's say a larg photo that it will take at leaset 10 secs to download). That image is loaded into a BitmapImaga object. This means that inside a thread you need to catch the DownloadCompleted event.... But if the thread is completed before the event is fired it will be destroyed and you will not have the image stored in that object... So how would you suggest to have that event captured inside the thread, so that the thread is not destroyed before the event is fired.... Or I am missing something!?

  • Anonymous
    September 04, 2006
    If you are doing an expensive operation like fetching an image over the network, there are basically two approaches. One is to do it synchronously on a background thread and the other is to use the asynchronous APIs. If you are using the asynchronous APIs, there's no need to set up your own background thread. Just dispatch the results to the UI thread in your DownloadCompleted event.

  • Anonymous
    September 27, 2006
    If you're doing WPF development, you really need to check out Dan Crevier 's series on DataModel-View-ViewModel.

  • Anonymous
    October 11, 2006
    I thought I should add a post with the full list of posts in the D-V-VM pattern. They are: DataModel-View-ViewModel

  • Anonymous
    January 06, 2007
    In part 3 , I showed code for StockModel, a DataModel for stocks. On the Max team, we are big believers