This article is compatible with the latest version of Silverlight.
1. Introduction
I recently found myself in the following situation. My boss came to me and asked me: “Dude, we have a big Silverlight project, performing a great number of CRUD operations. We need to find a unified way of showing Modal Dialogs in a MVVM manner, without using any code in the View. The logic of showing dialogs must reside in the ViewModel, not in the code-behind. Also do you remember our previous WinForms projects? There are several pretty useful classes, such as BindingSource and DataRowView, implementing BeginEdit, EndEdit and CancelEdit functionality out of the box. We need to bring this functionality in our Silverlight projects, however, this time on business objects level. It must be ready till tonight, otherwise you won’t get salary at the end of the month!!!”
Knowing my boss, the last sentence must have been a joke, or at least I hope to be a joke :) After working a few hours on the problem, I found a solution, which makes me feel pretty happy. That’s why I decided to share it with you guys.
2. Modal Dialogs and MVVM
So here is the general idea. First, we need to find a way to show modal dialogs. Fortunately, Silverlight 3 and Silverlight 4 provide us with ChildWindow. Of course, with the same success you can use dialogs (windows) provided by any third-party component vendor. However, the most important question is how to use a Modal Dialog inside the ViewModel and in the same time to remain decoupled. One of the most important OOP principles – The Dependency Inversion Principle teaches us to depend on abstractions not on concrete implementations. When we have static dependencies in our code, we can break them by using either an interface or a base class. Additionally, according to the MVVM pattern, the ViewModel doesn’t have to know about the View. Definitely we need an interface. Here it is:
So we need a DialogResult to determine whether the OK or Cancel button is clicked. We need to have the possibility to Close and Show the dialog; to be notified when the dialog is closed, in order to perform the subsequent actions. And finally, let’s take a look at the Content property. It is a little bit more special. Why? The answer is that there are two possible solutions of the current problem. In the first one, we can exchange the Content property with DataContext property. Or in other words, we need to create a new modal dialog for each entity. After that we just set the target entity to the dialog’s DataContext, like on the image below.
The second solution requires some additional work. Instead of having a separate child window for each entity, you could use UserControls. After that, you just set the UserControl to the dialog’s Content property.
Personally I prefer the second one, and namely it is demonstrated in this demo. However, the first one is also “playable”. Here is the IModalView interface. It is a quite simple, and provides a way to set the DataContext property of the UserControl, as well as to be notified when OK or Cancel button is pressed.
Finally, we need to bring in another level of abstraction on the scene. The final piece is the glue between the ModalDialog, the ModalView, and the ViewModel. This is the IModalDialogWorker interface. It provides only one method for showing dialogs and completely hiding the details of the implementation.
As you can see, when you show modal dialogs, you have to pass implementations of IModalDialog and IModalView interfaces, a dataContext (this is the currently edited entity – e.g. Person, Customer, Employee, etc). Finally, you need to pass an action that is invoked when the dialog is closed. The last parameter is optional (e.g. you can use that method to show custom MessageBox dialogs). Via the IModalDialog’s DialogResult property, you can obtain the dialog result.
The ModalDialogWorker implementation is quite simple. It is just a matter of coding, here it is:
public class ModalDialogWorker : IModalDialogWorker
{
public void ShowDialog<T>( IModalDialog modalDialog, IModalView modalView, T dataContext, Action<T> onClosed )
{
if ( modalDialog == null )
throw new ArgumentNullException( "modalDialog", "Cannot be null" );
if ( modalView == null )
throw new ArgumentNullException( "modalView", "Cannot be null" );
EventHandler onDialogClosedHandler = null;
EventHandler<ModalViewEventArgs> onViewClosedHandler = null;
if ( onClosed != null )
{
onDialogClosedHandler = ( s, a ) =>
{
modalDialog.Closed -= onDialogClosedHandler;
onClosed( dataContext );
};
onViewClosedHandler = ( s, a ) =>
{
modalDialog.Closed -= onDialogClosedHandler;
modalView.Closed -= onViewClosedHandler;
modalDialog.DialogResult = a.DialogResult;
//modalDialog.Close();
onClosed( dataContext );
};
modalDialog.Closed += onDialogClosedHandler;
modalView.Closed += onViewClosedHandler;
}
modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();
}
}
Now let’s see how to create IModalDialog and IModalView implementations. The ModalDialog is very simple. You just have to derive from ChildWindow and implement the IModalDialog interface.
public class ExtendedChildWindow : ChildWindow, IModalDialog
{
public void ShowDialog()
{
this.Show();
}
}
Note that the ExtendedChildWindow dialog is universal, you just have to set its content property with the concrete XXXModalView. For example, if you want to create a control for editing Person objects, you need to create a new UserControl and implement IModalView interface.
public partial class EditPersonControl : UserControl, IModalView
{
public EditPersonControl()
{
InitializeComponent();
}
public event EventHandler<ModalViewEventArgs> Closed;
protected virtual void OnClosed( ModalViewEventArgs e )
{
if ( this.Closed != null )
this.Closed( this, e );
}
private void btnOkClick( object sender, RoutedEventArgs e )
{
this.OnClosed( new ModalViewEventArgs( true ) );
}
private void btnCancelClick( object sender, RoutedEventArgs e )
{
this.OnClosed( new ModalViewEventArgs( false ) );
}
}
The only things you should do in the code-behind is to mark the control as IModalView implementation; to set the DialogResult to true, if user clicked OK, and to false if user clicked Cancel. Note that this is done in the code-behind because it is not part of the business logic, but of the UI logic.
The key moments in the ModalDialogWorker are these three lines of code:
modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();
3. “M” for Mighty
The question remains how to inject the IModalDialog, IModalVIew and IModalDialogWorker implementations into the ViewModel.
Fortunately, we have MEF in our tool belt. You need to mark all composable parts with the Export attribute and to specify contracts.
On the other side of the channel, the ViewModel, you have to specify the Imports. After that, using the ModalDialogWorker is just a piece of cake.
private void OnEditPersonCommandExecute()
{
this.ModalDialogWorker.ShowDialog<Person>(
this.ModalDialog, this.EditPersonControl, this.SelectedPerson, p =>
{
if ( this.ModalDialog.DialogResult.HasValue &&
this.ModalDialog.DialogResult.Value )
{
// OK
}
else
{
// Cancel
}
} );
}
Of course you could use any other discovering patterns.
4. Generic Implementation of IEditableObject
The next important bunch of questions is: What happens when the user edits objects? What should happen when he/she presses the OK or Cancel button? How to commit the changes? How to restore the original state of the object?
We need to commit or rollback changes through the BeginEdit, EndEdit and CancelEdit methods. That is easy. We can use IEditableObject interface. The most important problem is how to keep a snapshot of the object’s state. To address this bullet, we can use the Memento pattern. The function of the Memento pattern is to capture and externalize an object’s internal state so that the object can be restored to this state later, without violating encapsulation.
Well having this in mind, let’s create a new generic class named Memento<T>.
public class Memento<T>
{
private Dictionary<PropertyInfo, object> storedProperties = new Dictionary<PropertyInfo, object>();
public Memento( T originator )
{
this.InitializeMemento( originator );
}
public T Originator
{
get;
protected set;
}
public void Restore( T originator )
{
foreach ( var pair in this.storedProperties )
{
pair.Key.SetValue( originator, pair.Value, null );
}
}
private void InitializeMemento( T originator )
{
if ( originator == null )
throw new ArgumentNullException( "Originator", "Originator cannot be null" );
this.Originator = originator;
IEnumerable<PropertyInfo> propertyInfos = typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance )
.Where( p => p.CanRead && p.CanWrite );
foreach ( PropertyInfo property in propertyInfos )
this.storedProperties[ property ] = property.GetValue( originator, null );
}
}
The code is quite simple. First you are passing the originator (this is the object we want to track). In the InitializeMemento method, you are taking the internal state. All properties and their values are stored in a simple Dictionary<string,object>.
this.Originator = originator;
IEnumerable<PropertyInfo> propertyInfos = typeof( T ).GetProperties(
BindingFlags.Public | BindingFlags.Instance )
.Where( p => p.CanRead && p.CanWrite );
foreach ( PropertyInfo property in propertyInfos )
this.storedProperties[ property ] = property.GetValue( originator, null );
Finally, in the RestoreState method, we are taking the original values and restoring the object’s initial state.
public void Restore( T originator )
{
foreach ( var pair in this.storedProperties )
{
pair.Key.SetValue( originator, pair.Value, null );
}
}
The last piece of the puzzle is the Caretaker. The Caretaker is a simple wrapper. It implements the IEditableObject interface and has a reference to the generic Memento<T>. This is the place where the magic happens. However, the code is even simpler than the code for Memento<T>.
public class Caretaker<T> : IEditableObject
{
private Memento<T> memento;
private T target;
public T Target
{
get
{
return this.target;
}
protected set
{
if ( value == null )
{
throw new ArgumentNullException( "Target", "Target cannot be null" );
}
if ( Object.ReferenceEquals( this.Target, value ) )
return;
this.target = value;
}
}
public Caretaker( T target )
{
this.Target = target;
}
public void BeginEdit()
{
if ( this.memento == null )
this.memento = new Memento<T>( this.Target );
}
public void CancelEdit()
{
if ( this.memento == null )
throw new ArgumentNullException( "Memento", "BeginEdit() is not invoked" );
this.memento.Restore( Target );
this.memento = null;
}
public void EndEdit()
{
if ( this.memento == null )
throw new ArgumentNullException( "Memento", "BeginEdit() is not invoked" );
this.memento = null;
}
}
Now, you are free to use the Caretaker in the following manner:
Caretaker<Person> editableObject = new Caretaker<Person>( this.SelectedPerson );
editableObject.BeginEdit();
//.....
this.SelectedPerson.Name = "Pesho";
// Commit Changes
editableObject.EndEdit();
// -or CancelChanges
// editableObject.CancelEdit();
Let’s go back to our PersonViewModel, and update the OnEditPersonCommandExecute method:
private void OnEditPersonCommandExecute()
{
Caretaker<Person> editableObject = new Caretaker<Person>( this.SelectedPerson );
editableObject.BeginEdit();
this.ModalDialogWorker.ShowDialog<Person>(
this.ModalDialog, this.EditPersonControl, this.SelectedPerson, p =>
{
if ( this.ModalDialog.DialogResult.HasValue &&
this.ModalDialog.DialogResult.Value )
{
editableObject.EndEdit();
}
else
{
editableObject.CancelEdit();
}
} );
}
5. Final Words
You can download the source code from here.
The other goodies: Note that the whole business logic is located in the ViewModel. No actions in the code-behind. Also pay attention to the fact that at each point the ViewModel deals with interfaces, no concrete implementations. This is important, because it is not recommended to have a reference in the ViewModel to ChildWindow (for example). I am a big fan of MEF, that’s why I am using it wherever this is possible. However, marking the concrete implementations with the Export attribute doesn’t obligate you to use MEF for parts discovering. You are free to use any other pattern (approach). Finally, using DelegateCommand – this is standard, no need of explanation.
And the online demo. Select a person from the datagrid. Press the Edit button, a new modal dialog appears. This is happening thanks to the three interfaces presented in the article. Make some changes to the current Person object. Click either the OK or Cancel button. The changes are committed, or respectively rejected. This is happening thanks to the generic implementations of the IEditableObject and the Memento pattern.
So, that’s it. I know the problem for performing CRUD operations in Silverlight/WPF is not so simple and I hope that the code here will be useful for you. Feel free to experiment with it. If you have any idea for further improvements or questions, I will be happy if you share it with us.