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:
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).
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 CommandViewModel
s are stored by one CommunityViewModel
object (a class that represents a community of people), inside of its MemberCommands
collection.
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 CommandViewModel
s 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");
var syncContext = SynchronizationContext.Current;
if (syncContext == null)
throw new InvalidOperationException("...");
ThreadPool.QueueUserWorkItem(delegate
{
try
{
var payload = RetrievePeople();
if (payload != null)
syncContext.Post(
arg => CreateOutput(payload, output, transform, onCompleted), null);
}
catch
{
}
});
}
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()
{
Thread.Sleep(2500);
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);
_selectMemberCommand = new RelayCommand<Person>(this.SelectMember);
var dataSource = new PersonDataSource();
dataSource.RetrieveAndTransformAsync(
_memberCommandsInternal,
this.CreateCommandForPerson,
() => this.IsLoadingData = false
);
this.IsLoadingData = true;
}
void SelectMember(Person 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