Click here to Skip to main content
Click here to Skip to main content

From Russia with Love – Retrieving ViewModel Objects from a Model Assembly

, 7 Sep 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Reviews the “From Russia with Love” technique of simplifying the creation of ViewModel objects from other libraries, without compromising your MVVM architecture

Introduction

This article examines a way to easily transform data model objects into ViewModel objects, and safely add them to an observable collection, without introducing awkward assembly dependencies into an application. The technique makes use of generic delegates and the SynchronizationContext to maintain loose coupling between assemblies. This approach to transforming data types is applicable outside the realm of WPF and the Model-View-ViewModel (MVVM) pattern, but the article focuses on its use within a WPF application built using MVVM.

I nicknamed this technique “From Russia with Love” because it allows you to conveniently receive UI-friendly objects from a foreign, distant place in your application.

Background

This article assumes that the reader is familiar with building WPF applications based on the Model-View-ViewModel design pattern. The demo application, which is available for download at the top of this article, makes use of the ObservableObject and RelayCommand classes from my MVVM Foundation library on CodePlex. The MVVM Foundation DLL is included in the demo project.

The problem

When building an application structured according to the MVVM design pattern, it is often useful and necessary to have the Model and ViewModel types live in different assemblies. The classes that retrieve data objects (i.e. Model objects) often live in an assembly that has no knowledge of or access to the application’s ViewModel types. The ViewModel classes have intimate knowledge of the Model classes, and usually issue requests to classes that retrieve and instantiate Model data. Thus, the assemblies in which ViewModel classes reside reference the assemblies in which Model classes reside, but not vice versa.

This configuration is a natural and expected side-effect of the MVVM design pattern, wherein the Model classes represent the “pure” domain of the system being developed, and the ViewModel is an adapter between a user interface (UI) and the data model. A consequence of this setup is that the ViewModel classes are often burdened with the task of creating ViewModel objects that contain one or more Model objects. The logic that performs this task can easily lead to code bloat in the ViewModel classes, and can give them too much “code gravity” – which means that more and more code will end up being added to the ViewModel classes as time goes on.

A preferable solution would spread out the task of transforming Model objects into ViewModel objects across the parties involved, such that each party would contain reusable code responsible for the work most appropriate to it. Of course, using the term “most appropriate” indicates that this decision is open to interpretation. For the remainder of this article, I will examine a design that satisfies my opinion of how to best solve this problem. If your application’s needs are not met by the choices presented herein, you are free to abandon my approach and adopt that which best suits your specific needs.

Before moving forward, let’s first review the problem in more detail. What follows is a list of requirements that must be met by the solution:

  • The ViewModel should be able to issue a request for a set of Model objects, provide some logic that transforms a Model object to a ViewModel object, and never be bothered to perform any subsequent processing.
  • The ViewModel objects that are created should be added to an ObservableCollection<T> where T is the type of ViewModel being created.
  • The observable collection must be populated on the UI thread, as per the constraints imposed by WPF’s binding system.
  • The ViewModel object that issued the data retrieval request should be notified once all data objects have been retrieved and transformed into ViewModel objects and added to the observable collection. This gives it the opportunity to update the UI to indicate that the data loading process is complete.
  • The assembly that contains the Model types and transforms them into ViewModel objects cannot have a reference to the assembly containing the ViewModel types.

An Example of the Problem

Suppose the Model assembly contains a Person class:

/// <summary>
/// Model class that represents a person.
/// </summary>
public class Person
{
    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
}

Now imagine that the UI needs to display Person objects in a list, where a button represents each person, and clicking a button causes the application to “select” that person (what selection means in this context is irrelevant to the discussion).

MainWindow.png

Each button in the UI executes a command referenced by its Command property upon being clicked. This means that each Person data object needs to somehow be associated with a command object, so that when a button is clicked its command knows which person was selected. To satisfy that requirement of this application, let’s create a CommandViewModel class:

public class CommandViewModel
{
    public CommandViewModel(ICommand command, string displayName, object commandParameter)
    {
        this.Command = command;
        this.DisplayName = displayName;
        this.CommandParameter = commandParameter;
    }
    public ICommand Command { get; private set; }
    public object CommandParameter { get; private set; }
    public string DisplayName { get; private set; }
}

All of the CommandViewModels are stored by one CommunityViewModel object (a class that represents a community of people), inside of its MemberCommands collection.

/// <summary>
/// Returns a read-only collection of command objects, each of which represents a 
/// member of the community.  When a command executes, it 'selects' the associated member.
/// </summary>
public ReadOnlyObservableCollection<CommandViewModel> MemberCommands{ get; private set; }

Now let’s see how the UI displays a collection of these CommandViewModel objects:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.DataContext>
        <CollectionViewSource Source="{Binding Path=MemberCommands}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="DisplayName" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
    </ItemsControl.DataContext>    
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button
                Command="{Binding Command}"
                CommandParameter="{Binding CommandParameter}"
                Content="{Binding DisplayName}"
                Margin="4"
                />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

At this point we know about the data model (the Person class) and the View and ViewModel (an ItemsControl that displays each CommandViewModel in the MemberCommands property of a CommunityViewModel). Now we need to determine how these two pieces of the application are tied together. The CommunityViewModel class needs to somehow retrieve the Person objects and populate its MemberCommands collection with CommandViewModels that represent each Person.

The Solution

A method in the Model assembly that retrieves Person data can be passed a delegate that takes a Person parameter, and returns a CommandViewModel object. However, since the Model assembly cannot have a reference to the ViewModel assembly, one might expect this to lead to a compiler error because the Model assembly cannot reference ViewModel types. This problem can be solved via the use of generic delegates. Since the return type of the delegate can be left unspecified as a generic type parameter, the Model assembly can successfully compile even though it is being used to create ViewModel types of which it has no knowledge.

Here is PersonDataSource, from the Model assembly, which is used to perform this task:

public class PersonDataSource
{
    public void RetrieveAndTransformAsync<TOutput>(
        ObservableCollection<TOutput>   output, 
        Func<Person, TOutput>           transform, 
        Action                          onCompleted)
    {
        if (output == null)
            throw new ArgumentNullException("output");
        if (transform == null)
            throw new ArgumentNullException("transform");

        // One possible improvement would be to create an overload of this method that
        // has a SynchronizationContext parameter, so it can be invoked on any thread.
        var syncContext = SynchronizationContext.Current;
        if (syncContext == null)
            throw new InvalidOperationException("...");

        ThreadPool.QueueUserWorkItem(delegate
        {
            try
            {
                // Fetch the data objects.
                var payload = RetrievePeople();

                // Transform each data object and add it to the output collection, 
                // on the UI thread.
                if (payload != null)
                    syncContext.Post(
                      arg => CreateOutput(payload, output, transform, onCompleted), null);
            }
            catch
            {
                // Implement error processing here...
            }
        });
    }

    static void CreateOutput<TPayload, TOutput>(
        IEnumerable<TPayload>           payload,
        ObservableCollection<TOutput>   output,
        Func<TPayload, TOutput>         transform,
        Action                          onCompleted)
    {
        foreach (TPayload dataItem in payload)
        {
            TOutput outputItem = transform(dataItem);
            output.Add(outputItem);
        }

        if (onCompleted != null)
            onCompleted();
    }

    static Person[] RetrievePeople()
    {
        // Simulate network latency for the demo app...
        Thread.Sleep(2500);

        // In a real app this would call an external data service.
        // The data access call would not have to be async, because
        // this method is executed on a worker thread.
        return new Person[]
        {
            new Person("Franklin", "Argominion"),
            new Person("Douglas", "Mintagissimo"),
            new Person("Mertha", "Laarensorp"),
            new Person("Zenith", "Binklefoot"),
            new Person("Tommy", "Frankenfield"),
        };
    }
}

The PersonDataSource class provides support for both data retrieval and transformation. The logic for transforming each Person into a ViewModel object is given to it via the transform delegate, which is a parameter of the RetrieveAndTransformAsync method. Notice that it uses the current SynchronizationContext to marshal back to the UI thread, instead of using WPF’s Dispatcher, in case this code must be eventually used to support other UI platforms. I intentionally omitted any error handling logic, because that kind of code can vary a lot across different applications.

Now it’s time to see how the CommunityViewModel, back in the main EXE assembly, uses the PersonDataSource class.

public CommunityViewModel()
{
    _memberCommandsInternal = new ObservableCollection<CommandViewModel>();
    this.MemberCommands = 
       new ReadOnlyObservableCollection<CommandViewModel>(_memberCommandsInternal);

    // Create the command that is used by each CommandViewModel in MemberCommands.
    _selectMemberCommand = new RelayCommand<Person>(this.SelectMember);

    // Create the data source and begin a call to fetch and transform Person objects.
    var dataSource = new PersonDataSource();
    dataSource.RetrieveAndTransformAsync(
        _memberCommandsInternal,         // output
        this.CreateCommandForPerson,     // transform
        () => this.IsLoadingData = false // onCompleted
        ); 

    this.IsLoadingData = true;
}

void SelectMember(Person person)
{
    // In a real app this method would do something with the selected Person.
    string msg = String.Format("You selected '{0}'.", FormatName(person));
    MessageBox.Show(msg, "Congratulations!");
}

CommandViewModel CreateCommandForPerson(Person person)
{
    return new CommandViewModel(_selectMemberCommand, FormatName(person), person);
}

static string FormatName(Person person)
{
    return String.Format("{0}, {1}", person.LastName, person.FirstName);
}

Notice how CommunityViewModel only contains the logic necessary to wrap a Person in a CommandViewModel, as seen in its CreateCommandForPerson method. It does not need to handle the completion of the data access call, check for errors, marshal execution back to the UI thread, and iterate over the list of Person objects. All of that repetitive post-processing code has been conveniently consolidated in PersonDataSource. I find this separation of concerns to strike the right balance between having the ViewModel classes do too much versus having the Model classes know too much.

Revision History

  • September 7, 2009 – Published the article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
GeneralCommands and the ViewModel PinmemberMarc Schluper4-Dec-09 5:13 
GeneralRe: Commands and the ViewModel PinmvpJosh Smith4-Dec-09 5:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 7 Sep 2009
Article Copyright 2009 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid