WCF RIA Services DomainContext Abstraction Strategies–Say That 10 Times!

The DomainContext available with WCF RIA Services provides a lot of functionality that can help track object state and handle making calls from a Silverlight client to a DomainService. One of the questions I get quite often in our Silverlight training classes (and see often in various forums and other areas) is how the DomainContext can be abstracted out of ViewModel classes when using the MVVM pattern in Silverlight applications. It’s not something that’s super obvious at first especially if you don’t work with delegates a lot, but it can definitely be done. There are various techniques and strategies that can be used but I thought I’d share some of the core techniques I find useful.

To start, let’s assume you have the following ViewModel class (this is from my Silverlight Firestarter talk available to watch online here if you’re interested in getting started with WCF RIA Services):

public class AdminViewModel : ViewModelBase
{
    BookClubContext _Context = new BookClubContext();

    public AdminViewModel()
    {
        if (!DesignerProperties.IsInDesignTool)
        {
            LoadBooks();
        }
    }

    private void LoadBooks()
    {
        _Context.Load(_Context.GetBooksQuery(), LoadBooksCallback, null);
    }


    private void LoadBooksCallback(LoadOperation<Book> books)
    {
        Books = new ObservableCollection<Book>(books.Entities);
    }
}

Notice that BookClubContext is being used directly in the ViewModel class. There’s nothing wrong with that of course, but if other ViewModel objects need to load books then code would be duplicated across classes. Plus, the ViewModel has direct knowledge of how to load data and I like to make it more loosely-coupled. To do this I create what I call a “Service Agent” class. This class is responsible for getting data from the DomainService and returning it to a ViewModel. It only knows how to get and return data but doesn’t know how data should be stored and isn’t used with data binding operations.

An example of a simple ServiceAgent class is shown next. Notice that I’m using the Action<T> delegate to handle callbacks from the ServiceAgent to the ViewModel object. Because LoadBooks accepts an Action<ObservableCollection<Book>>, the callback method in the ViewModel must accept ObservableCollection<Book> as a parameter. The callback is initiated by calling the Invoke method exposed by Action<T>:

public class ServiceAgent
{

    BookClubContext _Context = new BookClubContext();

    public void LoadBooks(Action<ObservableCollection<Book>> callback)
    {
        _Context.Load(_Context.GetBooksQuery(), LoadBooksCallback, callback);
    }

    public void LoadBooksCallback(LoadOperation<Book> lo)
    {
        //Check for errors of course...keeping this brief
        var books = new ObservableCollection<Book>(lo.Entities);
        var action = (Action<ObservableCollection<Book>>)lo.UserState;
        action.Invoke(books);
    }
}

This can be simplified by taking advantage of lambda expressions. Notice that in the following code I don’t have a separate callback method and don’t have to worry about passing any user state or casting any user state (the user state is the 3rd parameter in the _Context.Load method call shown above).


public class ServiceAgent
{

    BookClubContext _Context = new BookClubContext();

    public void LoadBooks(Action<ObservableCollection<Book>> callback)
    {
        _Context.Load(_Context.GetBooksQuery(), (lo) =>
        {
            var books = new ObservableCollection<Book>(lo.Entities);
            callback.Invoke(books);
        }, null);
    }
}

A ViewModel class can then call into the ServiceAgent to retrieve books yet never know anything about the DomainContext object or even know how data is loaded behind the scenes:

public class AdminViewModel : ViewModelBase
{
    ServiceAgent _ServiceAgent = new ServiceAgent();

    public AdminViewModel()
    {
        if (!DesignerProperties.IsInDesignTool)
        {
            LoadBooks();
        }
    }

    private void LoadBooks()
    {
        _ServiceAgent.LoadBooks(LoadBooksCallback);
    }


    private void LoadBooksCallback(ObservableCollection<Book> books)
    {
         Books = books
    }
}

You could also handle the LoadBooksCallback method using a lambda if you wanted to minimize code just like I did earlier with the LoadBooks method in the ServiceAgent class.  If you’re into Dependency Injection (DI), you could create an interface for the ServiceAgent type, reference it in the ViewModel and then inject in the object to use at runtime. There are certainly other techniques and strategies that can be used, but the code shown here provides an introductory look at the topic that should help get you started abstracting the DomainContext out of your ViewModel classes when using WCF RIA Services in Silverlight applications.

comments powered by Disqus

9 Comments

  • Jan,

    Thanks for the info...good stuff on your ViewModelLocator post!

    Dan

  • Nice article! However as Jan also says, you can make a few improvements:
    1. Pass the value of the domaaincontext in the constructor (you may want to keep only one object of this type)
    2. Introduce interfaces for better testing

    @Jan: I don't see a problem with Load/SubmitOperation being sealed. In your mock implementation, you have only public methods and no callbacks (which are normally a result when using RIA). I mean, you do not need to use Load/SubmitOperation when mocking the service agent.

  • Boyan: Thanks for the suggestions....I absolutely agree but kept it simple on purpose since it was written up for a few specific people to help get them started based on some questions they had. Since I normally use constructor injection for things I may enhance it though in the near future. Appreciate it.

    Dan

  • Great post. I've been inundated with the same types of questions for a while now. In my case, I use DI with an IServiceBridge interface following the same pattern you describe which also allows me to unit test my ViewModel code by mocking the service implementation. It takes a while for devs that are new to the idea to gets their heads wrapped around it, but once they get it...

  • @Boyan yeah but when you wanna use the goodies from LoadOperation like Cancellation/Busystatus in your ViewModels then you have to expose them in your ServiceAgent and then you have to mock them as well.

  • The code above is similar to John Papa's code at PDC which had problems due to the use of a plain ObservableCollection. John's code was changed at the Firestarter event to use the new EntityList object. The corrected ServiceAgent would now like like this:

    public class ServiceAgent
    {

    BookClubContext _Context = new BookClubContext();

    public void LoadBooks(Action<ObservableCollection> callback)
    {
    _Context.Load(_Context.GetBooksQuery(), (lo) =>
    {
    var books = new EntityList(_Context.Books, lo.Entities);
    callback.Invoke(books);
    }, null);
    }
    }

  • Colin: That's definitely good stuff....good chatting with you on Twitter about it. So that everyone's aware, you'll need WCF RIA Services SP1 to work with the EntityList that Colin mentions (currently it's in Beta). The code in the post is specific to WCF RIA Service V1 (without the service pack). There are of course other ways to do it and ObservableCollection doesn't have to be used. I like to abstract my VM completely though so that it doesn't know anything about the data access technology used behind the scenes. Going forward, the EntityList will be the way to go though once SP1 is officially released. It derives from ObservableCollection making it super easy to use.

    Dan

  • so what is the actual problem with ObservableCollection ?? I missed it.

  • bitdisaster: There's no problem actually. But, people who don't know better may use it and not keep it in-sync with the DomainContext which could cause a problem. I personally don't think it's a big deal because we should know what we're doing there. I prefer to use ObservableCollection since it allows the VM to know absolutely nothing about how data is gathered allowed me to switch out my service calls without changing a single thing in the VM. The new EntityList in RIA Services SP1 looks promising though since it derives from ObservableCollection.

    Dan

Comments have been disabled for this content.