The question I’ve been fielding most lately is how to write a custom CollectionViewLoader. Once you see it done, it’s a pretty simple task. Without an example it’s hard to know where to start. In a previous post, I discussed how the view, loader, and source related to each other. In this post, I’ll show how that translates to the code you write.

The Loader as part of your View Model

Out of the three parts of the DomainCollectionView (DCV) triad, the loader is the piece mostly closely aligned with your View Model. In fact, I wrote the default implementation (the DomainCollectionViewLoader) with callbacks so you could put all the code you cared about directly in your VM. That said, feel free to blur the lines between your VM and your CollectionViewLoader until you find the balance of functionality and reusability you feel comfortable with.

The CollectionViewLoader

A good place to start is to take a look at public surface area of the CollectionViewLoader type you’ll be extending.

  public abstract class CollectionViewLoader
  {
    public event EventHandler CanLoadChanged;
    public event AsyncCompletedEventHandler LoadCompleted;

    public abstract bool CanLoad { get; }

    public abstract void Load(object userState);
  }

It’s a simple abstract class with one method, one property, and two events. The Load method is responsible for asynchronously loading data into the source collection of the collection view. Since the Load method is asynchronous, it needs to raise a LoadCompleted event when it completes. The CanLoad property tells the DCV whether it can safely invoke the Load method. There are a number of CanXx properties in the ICollectionView interface, and changes to the CanLoad property will propagate to all of them. For example, since re-sorting the DCV will result in a Load call, CanSort will be set to false when CanLoad is false.

The Sample

I’ve also had a number of questions lately about how to use the DCV with the MVVM pattern I proposed in a recent post. This will be a two-birds-with-one-stone sample where I’ll show how to write a custom CollectionViewLoader that works with the MVVM service pattern. It’s worth noting this sample is a variant of the one I published with my original DCV post.

I’ll start with the custom loader code and work back through the rest of the sample.

  public class SampleCollectionViewLoader<TEntity>
: CollectionViewLoader where TEntity : Entity { private Action<Action<ServiceLoadResult<TEntity>>, object> _load; private Action<object> _cancelLoad; private Action<ServiceLoadResult<TEntity>> _onLoadCompleted; private object _currentUserState; public SampleCollectionViewLoader( Action<Action<ServiceLoadResult<TEntity>>, object> load, Action<object> cancelLoad, Action<ServiceLoadResult<TEntity>> onLoadCompleted) { if (load == null) { throw new ArgumentNullException("load"); } if (onLoadCompleted == null) { throw new ArgumentNullException("onLoadCompleted"); } this._load = load; this._cancelLoad = cancelLoad; this._onLoadCompleted = onLoadCompleted; } public override bool CanLoad { get { return true; } } private object CurrentUserState { get { return this._currentUserState; } set { if (this._currentUserState != value) { if (this._cancelLoad != null) { this._cancelLoad(this._currentUserState); } } this._currentUserState = value; } } public override void Load(object userState) { if (!this.CanLoad) { throw new InvalidOperationException(
"Load cannot be called when CanLoad is false"); } this.CurrentUserState = userState; this._load(this.OnLoadCompleted, userState); } private void OnLoadCompleted(ServiceLoadResult<TEntity> result) { this._onLoadCompleted(result); if (this.CurrentUserState == result.UserState) { this.CurrentUserState = null; } base.OnLoadCompleted(result); } }

This custom CollectionViewLoader in this sample is pretty straightforward and follows the pattern used in the DomainCollectionViewLoader to call back into the view model. It has three callbacks; one for loading, one for canceling, and one for load completion. As you can see, the signature of the callbacks differs from the ones in the DomainCollectionViewLoader. This is necessary to work with the service layer in a natural way which I’ll explain more in the next section.

There are a couple more things to note about this implementation. First, I haven’t done anything with the CanLoad property. In the sample I include a IsGridEnabled flag in the SampleViewModel that serves roughly the same purpose (and behaves a little nicer from a UI perspective when using the standard Silverlight DataGrid). Second, the bulk of the code that’s not calling into the view model is related to cancellation. I’ll talk a little more about that at the end.

The next part of the sample to look at is the service interface. It’s pretty simple and encapsulates loading sample entities.

  public interface ISampleService
  {
    EntityContainer EntityContainer { get; }

    void LoadSampleEntities(
      QueryBuilder<SampleEntity> query,
      Action<ServiceLoadResult<SampleEntity>> callback,
      object state);
  }

Next, we’ll take a look at the view model. It’s going to be the place where the SampleCollectionViewLoader, the SampleService, and all the callbacks are tied together.

  public SampleViewModel()
  {
    this._source = new EntityList<SampleEntity>(
this._service.EntityContainer.GetEntitySet<SampleEntity>());
this._loader = new SampleCollectionViewLoader<SampleEntity>( this.LoadSampleEntities, this.CancelLoadSampleEntities, this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<SampleEntity>(
this._loader,
this._source); // Go back to the first page when the sorting changes INotifyCollectionChanged notifyingSortDescriptions = this.CollectionView.SortDescriptions; notifyingSortDescriptions.CollectionChanged += (sender, e) => this._view.MoveToFirstPage(); using (this.CollectionView.DeferRefresh()) { this._view.PageSize = 10; this._view.MoveToFirstPage(); } }

The source is created using an EntitySet provided by the EntityContainer in the ISampleService. The loader now creates a SampleCollectionViewLoader and passes in three view model callbacks. We’ll take a look at two of those callbacks next.

  private void LoadSampleEntities(
Action<ServiceLoadResult<SampleEntity>> onLoadCompleted,
object userState) { this.IsGridEnabled = false; this._service.LoadSampleEntities( new QueryBuilder<SampleEntity>().SortAndPageBy(this._view), onLoadCompleted, userState); } private void OnLoadSampleEntitiesCompleted(
ServiceLoadResult<SampleEntity> result) { this.IsGridEnabled = true; if (result.Error != null) { // TODO: handle errors } else if (!result.Cancelled) { this._source.Source = result.Entities; if (result.TotalEntityCount != -1) { this._view.SetTotalItemCount(result.TotalEntityCount); } } }

If you’ve followed my previous posts on the subject (linked prolifically above), these two implementations will look very familiar. The only significant difference is that the loader passes a completion callback and user state through to the LoadSampleEntities method for it to pass to the service layer. When the service load completes, it will tell the loader which will in-turn tell the view model.

A Note on Cancellation

Cancellation is a tricky subject on its own, but I’ll try to address how it fits into this pattern. Since a CollectionViewLoader is only dealing with one set of data, each subsequent load should cancel the previous one. In the sample for the MVVM pattern, I had cancellation in the service layer. It’s an approach that worked on the assumption you only invoke one current load per entity type. It’s pretty easy to see how this assumption fails to scale, though, so other approaches are necessary. If you find yourself using the same load method for multiple purposes, I’d recommend adding a cancel method to the service layer that looks like CancelLoadXx(object userState). You can then invoke this from the cancel callback you pass to the loader.

Summary

Like I mentioned at the beginning of this post, I expect you to write custom CollectionViewLoaders that support the kinds of things you want to do in your view model. Hopefully this post gives you a template to start from. Feel free to customize it however you see fit.

Here’s the source for the sample. It shows a little more detail on things fit together. Let me know what you think and if you have questions.

http://code.msdn.microsoft.com/Writing-a-Custom-e0769d37