The easiest way to build a Silverlight MVVM app that uses RIA Services is to use the Visual Studio project template installed by the Simple MVVM Toolkit.
Before getting started, be sure that you have installed the required prerequisites, including the Blend SDK, SQL Express and the Northwind sample database
(which you must attach to the SQL Express instance).
- Open Visual Studio 2010, then select File, New Project.

- Under the Silverlight category, select the Mvvm sub-category.
- Select the SimpleMvvmRiaService
project template.
- You will get a solution with three projects: a Silverlight client, an ASP.NET web host, and a Silverlight Test project.

- The Silverlight project includes a reference to the SimpleMvvmToolkit assembly,
- If references for Microsoft.Expression.Interactions and System.Windows.Interactivity are missing,
you need to install the
Microsoft Expression Blend SDK.
- If you press F5 to run the app, you will see a functioning Silverlight MVVM app that talks to WCF RIA Services.

- The Load, Add, Edit and Remove buttons are functional, with Add and Edit displaying a model dialog and Remove prompting the user for confirmation.
- The Load button executes a LoadItems method on the ItemListViewModel, which obtains items from a MockItemListServiceAgent.
- Set the Test project as the Startup project, then press F5 to execute unit tests against the ItemListViewModel.

- Now that you know what you get out of the box with the SimpleMvvmRiaServices project template, it’s time to incorporate your own classes.

- We’re going to start by adding a Models folder to the Web project.
- Right-click on the Models folder, select Add New Item, then select a new
ADO.NET Entity Data Model.
- We’re going to be using the Northwind database, so name it Northwind.edmx.
- Select a data connection to the Northwind database (or create a new one).

- Bring in both the Categories and Products tables.
- Then remove Description and Picture properties from Category.
- Also remove SupplierID, QuantityPerUnit, UnitsInStock, UnitsOnOrder, and ReorderLevel properties from Product.
- Before proceeding further you need to build the Web project.

- Now it’s time to add a domain service to the Web project. This will generate classes based on the data model, which are then projected to the client.
- Right-click on the Services folder and select Add New Item.
- Type Domain Service in the search box and select Domain Service Class.
- Name the class NorthwindDomainService.

- In the Add Domain Service dialog, check both Category and Product entities.
- Enable editing of Products and leave the option checked to generate associated metadata.

- You may wish to run this wizard again in the future, so I recommend adding the “partial” keyword to the NorthwindDomainService class.
- Right-click on the Services folder to add a class named MyNorthwinDomainService.cs, then remove “My” from the class name and make it a partial class.
- Add a method called GetProductsByCategory to the second MyNorthwinDomainService.cs that accepts a categoryId parameter (int).
- Return a query that filters products by the specified category and sorts them by ProductName.
- Insert the Include operator before the where clause, specifying the “Category” property (so that we can show CategoryName).
public partial class NorthwindDomainService
{
public IQueryable<Product> GetProductsByCategory(int categoryId)
{
var query = from p in this.ObjectContext.Products
.Include("Category")
where p.CategoryID == categoryId
orderby p.ProductName
select p;
return query;
}
}
- Next, you will need to open NorthwindDomainService.metadata.cs, go to the
ProductMetadata class and add an
Include attribute
to the Category property.
internal sealed class ProductMetadata
{
private ProductMetadata() { }
[Include]
public Category Category { get; set; }
// Other propertied elided for clarity ...
}
- If you build the project and show all files for the Silverlight project, you will see a Generated_Code folder that includes a file called SimpleMvvmRiaServices.Web.g.cs.
- In this file you will find a NorthwindDomainContext class as well as Category and Product entity classes.
- Now it’s time to work on the Silverlight project. Right-click on the Services folder to add an interface called IProductServiceAgent.
- Using IItemListServiceAgent as an example, add methods to retrieve, add and remove entities, as well as save and reject changes.
- Because of the asynchronous nature of invoking services in Silverlight, you should include a callback Action parameter to harvest results or an exception.
- Add, Remove and RejectChanges methods are local and do not require a callback parameter.
public interface IProductServiceAgent
{
// Retrieve categories
void GetCategories(Action<List<Category>, Exception> completed);
// Retrieve products
void GetProductsByCategory(int categoryId,
Action<List<Product>, Exception> completed);
// Insert product
void AddProduct(Product item);
// Remove product
void RemoveProduct(Product item);
// Save changes
void SaveChanges(Action<Exception> completed);
// Reject changes
void RejectChanges();
}
- Using MockItemListServiceAgent as an example, implement the IProductServiceAgent interface in a
MockProductServiceAgent class.
- In order to support async invocation you will need to use a BackgroundWorker in the Get methods.
- Wire up the BackgroundWorker DoWork event to return some fake entities
- Wire up the BackgroundWorker Completed event to invoke the completed parameter
- Include the ServiceAgentExport attribute with AgentType set to Mock.
- Add fields for mockCategories and mockProducts and initialize them in the ctor.
- For now do not implement SaveChanges or RejectChanges
- Rename AddItem and RemoveItem to AddProduct and RemoveProduct
// Add ServiceAgentExport attribute, setting AgentType to Mock
[ServiceAgentExport(typeof(IProductServiceAgent), AgentType = AgentType.Mock)]
public class MockProductServiceAgent : IProductServiceAgent
{
// Mock data
List<Category> mockCategories;
List<Product> mockProducts;
public MockProductServiceAgent ()
{
// Init mock data
mockCategories = new List<Category>
{
new Category { CategoryID = 1, CategoryName = "Beverages" },
new Category { CategoryID = 2, CategoryName = "Condiments" },
new Category { CategoryID = 3, CategoryName = "Confections" }
};
// Mock products
mockProducts = new List<Product>
{
new Product { ProductID = 1, ProductName = "Latte", CategoryID = 1,
Category = mockCategories[0] },
new Product { ProductID = 2, ProductName = "Capuccino", CategoryID = 1,
Category = mockCategories[0] },
new Product { ProductID = 3, ProductName = "Mustard", CategoryID = 2,
Category = mockCategories[1] },
new Product { ProductID = 4, ProductName = "Cake", CategoryID = 3,
Category = mockCategories[2] }
};
}
// Get items asynchonously using BackgroundWorker
public void GetCategories(Action<List<Category>, Exception> completed)
{
// Use background worker to simulate async operation
var bw = new BackgroundWorker();
// Handle DoWork event to perform task on a background thread
bw.DoWork += (s, ea) =>
{
// Simulate work by sleeping
Thread.Sleep(TimeSpan.FromSeconds(2));
// Set result to mock categories
ea.Result = mockCategories;
};
// Handle RunWorkerCompleted event by invoking completed callback
bw.RunWorkerCompleted += (s, ea) =>
{
if (ea.Error != null)
completed(null, ea.Error);
else
completed((List<Category>)ea.Result, null);
};
// Call RunWorkerAsync to begin operation
bw.RunWorkerAsync();
}
// Get items asynchonously using BackgroundWorker
public void GetProductsByCategory(int categoryId, Action<List<Product>,
Exception> completed)
{
// Use background worker to simulate async operation
var bw = new BackgroundWorker();
// Handle DoWork event to perform task on a background thread
bw.DoWork += (s, ea) =>
{
// Simulate work by sleeping
Thread.Sleep(TimeSpan.FromSeconds(2));
// Set result to mock categories
ea.Result = mockProducts;
};
// Handle RunWorkerCompleted event by invoking completed callback
bw.RunWorkerCompleted += (s, ea) =>
{
if (ea.Error != null)
completed(null, ea.Error);
else
completed((List<Product>)ea.Result, null);
};
// Call RunWorkerAsync to begin operation
bw.RunWorkerAsync();
}
// Add item
public void AddProduct(Product item)
{
mockProducts.Add(item);
}
// Remove item
public void RemoveProduct(Product item)
{
mockProducts.Remove(item);
}
// TODO: Implement mock saves (optional)
public void SaveChanges(Action<Exception> completed)
{
MessageBox.Show("SaveChanges not implemented");
}
// TODO: Implement mock change rejection (optional)
public void RejectChanges()
{
MessageBox.Show("RejectChanges not implemented");
}
}
- Now that you’ve created a mock service agent, you need to add a real one that interacts with the Domain Context class.
- Using ItemListServiceAgent as an example, create a ProductServiceAgent class that implements IProductServiceAgent
- Add a fields that is initialized to a new NorthwindDomainContext.
- In each of the “get” methods, GetCategories and GetProducts, call Load on the domain context, passing the query factory method
- In the callback set the load operation’s Error or Entities property and then invoke the completed callback.
- Rename AddItem and RemoveItem methods to AddProduct and RemoveProduct and uncomment the code
- Uncomment code for SaveChanges and RejectChanges methods
// Add ServiceAgentExport attribute, setting AgentType to Mock
[ServiceAgentExport(typeof(IProductServiceAgent), AgentType = AgentType.Real)]
public class ProductServiceAgent : IProductServiceAgent
{
// Add a field of type NorthwindDomainContext
NorthwindDomainContext domainContext = new NorthwindDomainContext();
// Load categories from domain context
public void GetCategories(Action<List<Category>, Exception> completed)
{
// Load GetCategoriesQuery
EntityQuery<Category> query = domainContext.GetCategoriesQuery();
domainContext.Load(query, loadOp =>
{
// Declare error and result
Exception error = null;
IEnumerable<Category> items = null;
// Set error or result
if (loadOp.HasError)
{
error = loadOp.Error;
}
else
{
items = loadOp.Entities;
}
// Invoke completion callback
completed(items.ToList(), error);
}, null);
}
// Load products from domain context
public void GetProductsByCategory(int categoryId, Action<List<Product>,
Exception> completed)
{
// Load GetProductsByCategoryQuery
EntityQuery<Product> query = domainContext
.GetProductsByCategoryQuery(categoryId);
domainContext.Load(query, loadOp =>
{
// Declare error and result
Exception error = null;
IEnumerable<Product> items = null;
// Set error or result
if (loadOp.HasError)
{
error = loadOp.Error;
}
else
{
items = loadOp.Entities;
}
// Invoke completion callback
completed(items.ToList(), error);
}, null);
}
// Call Add on domain context items
public void AddProduct(Product item)
{
domainContext.Products.Add(item);
}
// Call Remove on domain context items
public void RemoveProduct(Product item)
{
domainContext.Products.Remove(item);
}
// Save changes on the domain content if there are any
public void SaveChanges(Action<Exception> completed)
{
// See if any products have changed
if (domainContext.Products.HasChanges)
{
// Submit bulk update
domainContext.SubmitChanges(submitOp =>
{
// Declare error
Exception error = null;
// Set error or result
if (submitOp.HasError)
{
error = submitOp.Error;
}
// Invoke completion callback
completed(error);
}, null);
}
}
// Reject unsaved changes on domain context
public void RejectChanges()
{
if (this.domainContext.Products.HasChanges)
{
this.domainContext.RejectChanges();
}
}
}
- With the mock and real service agents complete, you need to add a ViewModel for products.
- Right-click on the ViewModels folder and select Add New Item
- Expand the Silverlight / Mvvm category, then select SimpleMvvmViewModel
- Name it ProductListViewModel

- Replace the IXxxServiceAgent comment with IProductServiceAgent
- Using ItemListViewModel as an example, add event handlers for SaveChanges and ItemsSaved
public event EventHandler<NotificationEventArgs<object, bool>> SaveChangesNotice;
public event EventHandler<NotificationEventArgs> ItemsSavedNotice;
- Using the mvvmprop
code snippet, add a Categories property (ObservableCollection<Category>) and a
SelectedCategory property.
- Notice how the snippet uses a lambda expression for firing the PropertyChanged event in a type-safe manner
- Using the mvvmprop code snippet, also add a
Products property (ObservableCollection<Product>) and a
SelectedProduct property.
private ObservableCollection<Category> categories;
public ObservableCollection<Category> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(vm => vm.Categories);
}
}
private Category selectedCategory;
public Category SelectedCategory
{
get { return selectedCategory; }
set
{
selectedCategory = value;
NotifyPropertyChanged(vm => vm.SelectedCategory);
}
}
private ObservableCollection<Product> products;
public ObservableCollection<Product> Products
{
get { return products; }
set
{
products = value;
NotifyPropertyChanged(vm => vm.Products);
}
}
private Product selectedProduct;
public Product SelectedProduct
{
get { return selectedProduct; }
set
{
selectedProduct = value;
NotifyPropertyChanged(vm => vm.SelectedProduct);
}
}
- Use the mvvmprop code snippet to add the following properties:
- IsBusy, CanLoad, CanAdd, CanEdit, CanRemove
private bool isBusy;
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
NotifyPropertyChanged(m => m.IsBusy);
}
}
private bool canLoad = true;
public bool CanLoad
{
get { return canLoad; }
set
{
canLoad = value;
NotifyPropertyChanged(m => m.CanLoad);
}
}
private bool canAdd;
public bool CanAdd
{
get { return canAdd; }
set
{
canAdd = value;
NotifyPropertyChanged(m => m.CanAdd);
}
}
private bool canEdit;
public bool CanEdit
{
get { return canEdit; }
set
{
canEdit = value;
NotifyPropertyChanged(m => m.CanEdit);
}
}
private bool canRemove;
public bool CanRemove
{
get { return canRemove; }
set
{
canRemove = value;
NotifyPropertyChanged(m => m.CanRemove);
}
}
- Copy over the SetCanProperties helper method
- Invoke it from the SelectedProduct and IsBusy property setters
private void SetCanProperties()
{
CanLoad = !IsBusy;
CanAdd = !IsBusy;
CanEdit = !IsBusy && SelectedProduct != null;
CanRemove = !IsBusy && SelectedProduct != null;
}
- Add a LoadCategories
method to the ViewModel, invoking GetCategories on the serviceAgent.
- In the completion callback invoke a CategoriesLoaded method to set Categories and SelectedCategory properties
- For an example, see LoadItems and ItemsLoaded methods in ItemListViewModel.cs.
- Repeat this for a LoadProducts
method, calling GetProductsByCategory on the serviceAgent.
- Call a ProductsLoaded method in the completion callback.
- Repeat this for a SaveChanges
method, calling SaveChanges on the serviceAgent.
- Notify the view of pending changes and handle save in a callback
- Call a ItemsSaved method in the completion callback.
- In ItemsSaved method notify view that items were successfully saved or that there was an error
public void LoadCategories()
{
// Load items
serviceAgent.GetCategories
(
(entities, error) => CategoriesLoaded(entities, error)
);
// Reset property
Categories = null;
// Flip busy flag
IsBusy = true;
}
public void LoadProducts()
{
// Load items
serviceAgent.GetProductsByCategory
(
SelectedCategory.CategoryID,
(entities, error) => ProductsLoaded(entities, error)
);
// Reset property
Categories = null;
// Flip busy flag
IsBusy = true;
}
public void SaveChanges()
{
// Prompt the user to save changes if there are any
Notify(SaveChangesNotice, new NotificationEventArgs<object, bool>
("There are unsaved changes. Do you wish to save?", null,
confirm =>
{
if (confirm)
{
// Save changes
serviceAgent.SaveChanges
(error => ItemsSaved(error));
}
}));
}
private void CategoriesLoaded(List<Category> entities, Exception error)
{
// If no error is returned, set the model to entities
if (error == null)
{
Categories = new ObservableCollection<Category>(entities);
}
// Otherwise notify view that there's an error
else
{
NotifyError("Unable to retrieve items", error);
}
// Set SelectedItem to the first item
if (Categories.Count > 0)
{
SelectedCategory = Categories[0];
}
// We're done
IsBusy = false;
}
private void ProductsLoaded(List<Product> entities, Exception error)
{
// If no error is returned, set the model to entities
if (error == null)
{
Products = new ObservableCollection<Product>(entities);
}
// Otherwise notify view that there's an error
else
{
NotifyError("Unable to retrieve items", error);
}
// Set SelectedItem to the first item
if (Products.Count > 0)
{
SelectedProduct = Products[0];
}
// We're done
IsBusy = false;
}
private void ItemsSaved(Exception error)
{
if (error == null)
{
// Notify view products were saved successfully
Notify(ItemsSavedNotice, new NotificationEventArgs
("Items were successfully saved"));
}
else
{
// Notify view if there's an error
NotifyError("Unable to save items", error);
}
// We're done
IsBusy = false;
}
- Also place Add, Remove and RejectChanges methods on the ProductListViewModel
- Call corresponding methods on the serviceAgent
- Use the methods in ItemListViewModel as an example, replacing Item(s) with Product(s)
- In RejectChanges call RejectChanges on the serviceAgent, then call LoadProducts
public void Add(Product item)
{
if (Products != null)
{
serviceAgent.AddProduct(item);
Products.Add(item);
SelectItem(item);
SetCanProperties();
}
}
public void Remove()
{
if (SelectedProduct != null)
{
serviceAgent.RemoveProduct(SelectedProduct);
Products.Remove(SelectedProduct);
SelectItem(null);
SetCanProperties();
}
}
public void RejectChanges()
{
serviceAgent.RejectChanges();
LoadProducts();
}
- Make sure to build the Silverlight project and correct any compilation errors
- Open InjectedViewModelLocator.cs, found in the Locators folder in the Silverlight project
- Using the mvvminjectedlocator
code snippet add the ProductListViewModel as a property
- Specify IProductListServiceAgent for the service agent interface
- Uncomment the Debug.Assert statement and move it to the ctor in order to verify creation of the service agent
- Also set the agentType field to AgentType.Real
// Create ProductListViewModel on demand
[ImportMany]
public Lazy<IProductServiceAgent, IServiceAgentMetadata>[] ProductServiceAgents { get; set; }
public ProductListViewModel ProductListViewModel
{
get
{
var serviceAgent = ProductServiceAgents
.Where(sa => sa.Metadata.AgentType == agentType).FirstOrDefault();
ProductListViewModel viewModel = null;
if (serviceAgent != null) viewModel = new ProductListViewModel(serviceAgent.Value);
else if (DesignerProperties.IsInDesignTool) viewModel = new ProductListViewModel();
return viewModel;
}
}
- Now that you have the ProductListViewModel constructed, you can create a unit test for it in the Test project
- Right-click on the Test project and select Add, New Item
- Choose the Silverlight / Mvvm category and select SimpleMvvmViewModelTests
- Name the file ProductListViewModelTests.cs

- Complete the TODO items to flesh out a LoadCategoriesTest
method
- In the ctor create the InjectedViewModelLocator and retrieve the ProductListViewModel
- In the test method handle an error notice from the VM by failing the test
- Handle the PropertyChanged event on Categories by completing the test
- There are some helper methods in the SilverlightTest base class you call in order to perform the needed steps for a unit test
- Call EnqueueCallback, executing LoadCategories on the ViewModel
- Call this.EnqueueConditional, passing a 10 second timeout
- Call EnqueueCallback to assert that the Categories property has been set
- Call EnqueueTestComplete to terminate the unit test
[TestClass]
public class ProductListViewModelTests : SilverlightTest
{
// Add ViewModel field
ProductListViewModel viewModel;
// Initialize ViewModel
public ProductListViewModelTests()
{
// Create locator
var locator = new InjectedViewModelLocator();
locator.AgentType = AgentType.Mock;
// Get ViewModel
this.viewModel = locator.ProductListViewModel;
Assert.IsNotNull(viewModel, "ServiceAgent not injected for ViewModel");
}
[TestMethod, Asynchronous]
public void LoadCategoriesTest()
{
// Completion flag
bool completed = false;
// Handle error
viewModel.ErrorNotice += (s, ea) => Assert.Fail(ea.Data.Message);
// Handle property change
viewModel.PropertyChanged += (s, ea) =>
{
if (ea.PropertyName == "Categories"
&& viewModel.Categories != null) completed = true;
};
// Call ViewModel method
EnqueueCallback(() => viewModel.LoadCategories());
// Wait for completion with timeout
int timeoutSeconds = 10; // Debugging: Timeout.Infinite
this.EnqueueConditional(() => completed, timeoutSeconds);
// Perform Asserts
EnqueueCallback(() =>
{
Assert.IsNotNull(viewModel.Categories);
});
// Complete test
EnqueueTestComplete();
}
}
- Set the Test project as the startup project and press F5 to run it. All tests should pass.
- Then reset the Web project as the startup project

- Last but not least, we are going to add a ProductListView to the Silverlight project.
- Right-click on the Views folder, select Add New Item, then choose
Silverlight Page
- Name the file ProductListView.cs

- To use the XML snippets installed by the toolkit you need to open the view using the XML Text Editor
- Right-click on ProductListView.xaml and select Open With, then choose XML Editor from the list
- First place the cursor in the xml namespaces section, then right-click and select Inert Snippet
- Select the mvvmxmlns snippet to insert the Blend namespaces
- Then use the mvvmcontext snippet to bind the page’s DataContext to the ProductListViewModel property of the ViewModelLocator
- Use the mvvmtrigger snippet to insert an event trigger for the
Loaded event on the page to call the LoadCategories
method
<!--EndFragment--> - The remaining effort for the ViewModel consists of laying out some nested StackPanels with a combo box, buttons and a DataGrid
- Using ItemListView.xaml as an example, change the root Grid to a StackPanel, then insert a nested horizontal StackPanel
- Insert a combobox with its ItemsSource bound to Categories and SelectedItem bound to SelectedCategory (Mode=TwoWay)
- Set DisplayMemberPath on the combobox to to CategoryName
- Copy xaml for the Load, Add, Edit, Remove, SaveChanges and RejectChanges buttons
- Make sure to set IsEnabled to True on the SaveChanges and RejectChanges buttons
- Modify the event trigger on the Load button to call the LoadProducts method
- Drag a DataGrid from the toolbox to a place beneath the StackPanel containing the combobox and buttons
- Bind ItemsSource and SelectedItem to Products and SelectedProduct (Mode=TwoWay)
- Add column bound to Product properties
- Copy the <Grid> element with a BusyIndicator from ItemListView.xaml to beneath the DataGrid
- Below is the resulting XAML for the ProductListView. Highlighted sections show inserts from
XML snippets.
<navigation:Page x:Class="SimpleMvvmRiaServices.Views.ProductListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
Title="Products"
DataContext="{Binding Source={StaticResource Locator}, Path=ProductListViewModel}" >
<!-- Load categories to populate the combobox -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction
TargetObject="{Binding}"
MethodName="LoadCategories"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- Button Style -->
<navigation:Page.Resources>
<Style TargetType="Button">
<Style.Setters>
<Setter Property="Height" Value="23"/>
<Setter Property="Width" Value="60"/>
<Setter Property="Margin" Value="5,0,0,0"/>
</Style.Setters>
</Style>
</navigation:Page.Resources>
<StackPanel x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal">
<ComboBox Height="23" Name="categoriesComboBox" Width="120"
ItemsSource="{Binding Path=Categories}"
SelectedItem="{Binding Path=SelectedCategory, Mode=TwoWay}"
DisplayMemberPath="CategoryName"/>
<Button Name="loadButton"
Content="Load"
IsEnabled="{Binding CanLoad}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction
TargetObject="{Binding}"
MethodName="LoadProducts"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Name="addButton"
Content="Add"
IsEnabled="{Binding CanAdd}" Click="addItemButton_Click" />
<Button Name="editButton"
Content="Edit"
IsEnabled="{Binding CanEdit}" Click="editItemButton_Click" />
<Button Name="removeButton"
Content="Remove"
IsEnabled="{Binding CanRemove}" Click="removeItemButton_Click" />
<!-- Set IsEnabled to True -->
<Button Name="saveChangesButton"
Content="Save Changes"
IsEnabled="True" Width="94">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction
TargetObject="{Binding}"
MethodName="SaveChanges"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<!-- Set IsEnabled to True -->
<Button Name="rejectChangesButton"
Content="Reject Changes"
IsEnabled="True" Width="94">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction
TargetObject="{Binding}"
MethodName="RejectChanges"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<sdk:DataGrid AutoGenerateColumns="False" Name="productsDataGrid" IsReadOnly="True"
ItemsSource="{Binding Path=Products}"
SelectedItem="{Binding Path=SelectedProduct, Mode=TwoWay}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Path=ProductID}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="Product Id" />
<sdk:DataGridTextColumn Binding="{Binding Path=ProductName}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="Product Name" />
<sdk:DataGridTextColumn Binding="{Binding Path=UnitPrice}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="Unit Price" />
<sdk:DataGridTextColumn Binding="{Binding Path=Category.CategoryName}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="Category" />
<sdk:DataGridCheckBoxColumn Binding="{Binding Path=Discontinued}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="Discontinued" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<Grid>
<!-- Bind IsBusy to IsBusy -->
<toolkit:BusyIndicator Name="isBusyIndicator" Height="80" Width="200"
IsBusy="{Binding IsBusy}" Margin="152,39,148,-39" />
</Grid>
</StackPanel>
</navigation:Page>
- Add, Edit and Remove buttons have Click event handlers. To insert the code behind methods, right-click on each handler in the XAML and select
Navigate To EventHandler
- Add a field for ProductListViewModel and grab it from the DataContext in the code-behind ctor
- In the ctor also subscribe to notifications from the ViewModel for ErrorNotice, SaveChangesNotice and ItemsSavedNotice
- Paste code for the handler methods from ItemListView.xaml.cs
public partial class ProductListView : Page
{
// ViewModel
ProductListViewModel viewModel;
public ProductListView()
{
InitializeComponent();
// Get vm from data context
viewModel = (ProductListViewModel)DataContext;
// Wire up handlers for vm notifications
viewModel.ErrorNotice += OnErrorNotice;
viewModel.SaveChangesNotice += OnSaveChangesNotice;
viewModel.ItemsSavedNotice += OnItemsSavedNotice;
}
void OnErrorNotice(object sender, NotificationEventArgs<Exception> e)
{
// Show user message string
MessageBox.Show(e.Message, "Error", MessageBoxButton.OK);
// Trace information
Debug.WriteLine(e.Data.ToString());
}
void OnSaveChangesNotice(object sender, NotificationEventArgs<object, bool> e)
{
// Prompt user to save changes
var mbResult = MessageBox.Show("Save changes?", "Save Changes", MessageBoxButton.OKCancel);
// Call back ViewModel with response
e.Completed(mbResult == MessageBoxResult.OK);
}
void OnItemsSavedNotice(object sender, NotificationEventArgs e)
{
// Inform user product was saved
MessageBox.Show(e.Message, "Save Changes", MessageBoxButton.OK);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void addItemButton_Click(object sender, RoutedEventArgs e)
{
}
private void editItemButton_Click(object sender, RoutedEventArgs e)
{
}
private void removeItemButton_Click(object sender, RoutedEventArgs e)
{
}
}
- Edit MainPage.xaml by setting the CommandParameter of the items hyperlink button to ProductListView
- If you run the app and click the button the ProductListView should appear
- Select a category and click the Load button. Products for that category should fill the data grid.

- Now we’re going to create a ProductDetailViewModel with a corresponding ProductDetailView for adding and editing an individual product.
- Right-click on the ViewModels folder and select Add New Item.
- Then expand the Silverlight/ Mvvm category and select SimpleMvvmViewModelDetail
- Name it ProductDetailViewModel

- Replace the /* DetailType */ comment with
Product, both in the class type argument and in the ctor
- Remove the serviceAgent field and the ctor that accepts serviceAgent
- Use the mvvmprop
code snippet to insert a Categories property (ObservableCollection<Category>)
public class ProductDetailViewModel : ViewModelDetailBase<ProductDetailViewModel, Product>
{
#region Initialization and Cleanup
// Default ctor
public ProductDetailViewModel() { }
// Ctor to set base.Model to DetailType
public ProductDetailViewModel(Product model)
{
base.Model = model;
}
#endregion
#region Notifications
// Add events to notify the view or obtain data from the view
public event EventHandler<NotificationEventArgs<Exception>> ErrorNotice;
#endregion
#region Properties
// Add properties using the mvvmprop code snippet
private ObservableCollection<Category> categories;
public ObservableCollection<Category> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(vm => vm.Categories);
}
}
#endregion
- Add a new Silverlight Child Window to the Views folder and name it ProductDetailView.xaml

- Copy the label and textbox styles from ItemDetailView.xaml.
- Copy the nested Grid from ItemDetailView.xaml and paste it above the buttons.
- Add three rows to the Grid, for a total of five rows
- Specify 5 labels: Product ID, Product Name, Category, Unit Price, Discontinued
- Specify corresponding textboxes bound to Model.ProductID,
Model.ProductName and Model.UnitPrice
- Insert a combobox and name it categoriesComboBox
- Bind ItemsSource the Categories property
- Set DisplayMemberPath to CategoryName
- Bind SelectedValue to Model.CategoryID
- Set SelectedValuePath to CategoryID
<controls:ChildWindow x:Class="SimpleMvvmRiaServices.Views.ProductDetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
Width="300" Height="260"
Title="Product" >
<!-- Label and TextBox styles -->
<sdk:ChildWindow.Resources>
<Style TargetType="sdk:Label">
<Style.Setters>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="10,0,0,0"/>
</Style.Setters>
</Style>
<Style TargetType="TextBox">
<Style.Setters>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="5"/>
</Style.Setters>
</Style>
</sdk:ChildWindow.Resources>
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="136*" />
<ColumnDefinition Width="242*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<sdk:Label Content="Product ID:" Grid.Row="0"/>
<sdk:Label Content="Product Name:" Grid.Row="1" />
<sdk:Label Content="Category:" Grid.Row="2" />
<sdk:Label Content="Unit Price:" Grid.Row="3" />
<sdk:Label Content="Discontinued:" Grid.Row="4" />
<!-- Bind controls to Model properties -->
<TextBox Name="productId" Text="{Binding Path=Model.ProductID, Mode=TwoWay}"
Grid.Column="1" Grid.Row="0" Height="28" Width="150"
IsReadOnly="True" Background="LightGray" />
<TextBox Name="productName" Text="{Binding Path=Model.ProductName, Mode=TwoWay}"
Grid.Column="1" Grid.Row="1" Height="28" Width="150"/>
<TextBox Name="unitPrice" Text="{Binding Path=Model.UnitPrice, Mode=TwoWay}"
Grid.Column="1" Grid.Row="3" Height="28" Width="150"/>
<CheckBox Name="discontinued" IsChecked="{Binding Path=Model.Discontinued, Mode=TwoWay}"
Height="16" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Column="1" Grid.Row="4" />
<ComboBox Name="categoryComboBox" Grid.Column="1" Grid.Row="2" Margin="5,5,5,5"
Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="150"
ItemsSource="{Binding Path=Categories}"
DisplayMemberPath="CategoryName"
SelectedValue="{Binding Path=Model.CategoryID, Mode=TwoWay}"
SelectedValuePath="CategoryID"/>
</Grid>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click"
Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</controls:ChildWindow>
- Open the code-behind for ProductDetailView and add a ctor that accepts ProductDetailViewModel
- Set DataContext to the incoming ProductDetailViewModel
- Invoke the default ctor
// Set DataContext to ProductDetailViewModel
public ProductDetailView(ProductDetailViewModel viewModel)
: this()
{
// Set data context to view model
DataContext = viewModel;
}
- Starting at line 70 in ItemListView.xaml.cs, copy code for Add, Edit and Remove button handlers to
ProductListView.xaml.cs.
- Paste over the button click handlers for add, edit and remove.
- Modify the code to create a ProductDetailViewModel and pass it to the ctor of a ProductDetailView, which you show modally
- Make sure to set the Categories property on detailModel to viewModel.Categories
- Notice how for edit mode we call BeginEdit, then either EndEdit or CancelEdit
// Add item
private void addItemButton_Click(object sender, RoutedEventArgs e)
{
// Create a product detail model
Product newItem = new Product();
ProductDetailViewModel detailModel = new ProductDetailViewModel(newItem);
// Set categories
detailModel.Categories = viewModel.Categories;
// Show ProductDetail view
ProductDetailView itemDetail = new ProductDetailView(detailModel);
itemDetail.Closed += (s, ea) =>
{
if (itemDetail.DialogResult == true)
{
viewModel.Add(newItem);
}
};
itemDetail.Show();
}
// Edit item
private void editItemButton_Click(object sender, RoutedEventArgs e)
{
// Exit if no product selected
if (viewModel.SelectedProduct == null) return;
// Create a product detail model
ProductDetailViewModel detailModel =
new ProductDetailViewModel(viewModel.SelectedProduct);
// Set categories
detailModel.Categories = viewModel.Categories;
// Start editing
detailModel.BeginEdit();
// Show ProductDetail view
ProductDetailView itemDetail = new ProductDetailView(detailModel);
itemDetail.Closed += (s, ea) =>
{
if (itemDetail.DialogResult == true)
{
// Confirm changes
detailModel.EndEdit();
}
else
{
// Reject changes
detailModel.CancelEdit();
}
};
itemDetail.Show();
}
// Remove item
private void removeItemButton_Click(object sender, RoutedEventArgs e)
{
// Exit if no product selected
if (viewModel.SelectedProduct == null)
{
return;
}
// Confirm remove, then remove
if (MessageBox.Show("Are you sure?", "Remove Item", MessageBoxButton.OKCancel)
== MessageBoxResult.OK)
{
viewModel.Remove();
}
}
}
- Run the app and click the Edit button to increase the unit price of the selected product.
- Click the Add button to add a new product
- Click the Remove button to remove an existing product
- Click the Reject Changes button to abandon pending changes
- Repeat the editing, adding and removal
- Click the Save Changes button to persist inserts, deleted and updates
- Batch changes are propagated to the service in one trip and persisted to the database in a single transaction

With the assistance of the Simple MVVM Toolkit, you have now built a real-world Silverlight application based on the MVVM pattern that leverages WCF RIA Services for entity retrieval and persistence. This includes a unit test project that relies
on the injected ViewModel locator to use a mock service agent. You have also used events for two-way communication between a View and its ViewModel.