Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / unit-testing

MVVM Silverlight Application with Entity Framework and WCF Service

4.76/5 (9 votes)
13 Jun 2016CPOL11 min read 42.3K   1.6K  
MVVM Silverlight app with EF and WCF Service

Introduction

This article is the second in my series about constructing an application using Expression Blend and Silverlight for the front-end which obtains data from a WCF service and SQL Server back-end. This second article focuses on creating a similar application to the one I demonstrated in my previous article except that it uses the Model-View-ViewModel (MVVM) design pattern. The MVVM is a specialization of Martin Fowler's Presentation Model. The ViewModel holds a certain type of data and commands. The VM does not know where the data comes from or how it is displayed. Controllers from the Model-View-Controller pattern on the other hand publish and listen for events. They control what data is shown and where in the UI.

Also, in this article, I will discuss the related concepts of services, commands, behaviors, interfaces, view model locators, and Inversion of Control. To explain the concepts, I will mostly use code-behind initially.

Table of Contents

  1. Creating a New Project and Service
  2. Add Silverlight Pages
  3. A Simple MVVM Implementation
    • Separating our Service Calls
    • Separate Out More Code from the UI Using Commands
    • IoC and ViewModel Locators
  4. Adding Exception Handling Capabilities

1. Creating a New Project and Service

Create a new project in Visual Studio, and select the Silverlight Navigation Application:

854816/NavApp.png

Next, choose to host the application in a new website. Do not choose to Enable WCF RIA Services, because at the time of writing of this article, Microsoft has discontinued support of this technology. If you still desire to implement RIA Services, please consider this open source implementation: Open RIA Services.

854816/NotWcfRia.png

Then, if you haven't already done so, setup the AdventureWorks database and vProductProductInventory view as explained in section 3 of my previous article. Also, get the WcfEntitiesSample.Model.Silverlight, WcfEntitiesSample.Model.Net and WcfEntitiesSample.Data projects and add them to this new solution as explained in the article.

Your Silverlight user-interface accesses and updates the data in the database through a SOAP-based WCF web service. Right-click on your MvvmSample.Web project and choose to add the WCF Service:

854816/ProductsWcfService.png

Now add a new method to the WcfEntitiesSample.Model.Net ProductManager.cs class, to get multiple products at once:

C#
public IList<Product> GetProducts()
{
    IEnumerable<Product> products = null;

    var db = new AdventureWorks2008R2Entities();
    products = db.vProductProductInventories
        .Select(p => new Product
        {
            ProductID = p.ProductID,
            Name = p.Name,
            InventoryCount = p.InventoryCount,
            Description = p.Description
        });

    return products.ToList();
}

Now go back to your MvvmSample.Web project and add a reference to the WcfEntitiesSample.Model.Net project:

854816/AddReferenceToModel.png

Then open up the IProductService.cs and ProductService.svc.cs files and add the following code. Please note that the AspCompatibilityRequirements requires the System.ServiceModel.Activation namespace.

C#
[ServiceContract]
public interface IProductsService
{
    [OperationContract]
    Product GetProductByID(int id);

    [OperationContract]
    IEnumerable<Product> GetProducts();
}

[AspNetCompatibilityRequirements(RequirementsMode =
                    AspNetCompatibilityRequirementsMode.Allowed)]
public class ProductsService : IProductsService
{
    public Product GetProductByID(int id)
    {
        var mgr = new ProductManager();
        return mgr.GetProductById(id);
    }

    public IList<Product> GetProducts()
    {
        var mgr = new ProductManager();
        return mgr.GetProducts();
    }
}

If there are red lines beneath the Product class name, then right-click one of those items and choose Resolve:

854816/ResolveType.png

The web service returns a list of products from the database. You could further filter the results if desired using LINQ in the ProductManager.cs class.

Now do a build and add a service reference to the Silverlight application from the new service.

Also, add a stored procedure to the database, so that later in the article, I can demonstrate the use of the ICommand interface:

SQL
CREATE PROCEDURE [dbo].[prProductUpdate]
    @ProductID int,
    @Name varchar(30) 
AS
BEGIN
    UPDATE [AdventureWorks2008R2].[Production].[Product]
        SET    [Name] = @Name
        WHERE [ProductID] = @ProductID
END        

Next double-click on the AdventureWorksEntites.edmx file, and right-click and choose "Update Model from Database..." and choose the procedure you just created:

854816/UpdateModelFromDatabase.png

Now go to your ProductManager.cs class, and add the following function:

C#
public void UpdateProductName(int productID, string name)
{
    var db = new AdventureWorks2008R2Entities();
    db.prProductUpdate(productID, name);
}

Now add a new function to your web service interface and svc files:

C#
// Add to interface
[OperationContract]
  void UpdateProductName(int id, string name);

// Add to *.svc.cs file:
public void UpdateProductName(int id, string name)
  {
      var mgr = new ProductManager();
      mgr.UpdateProductName(id, name);
  }

Next, go to your MvvmSample Silverlight application and update the Service Reference.

2. Add Silverlight Pages

In the presentation layer MvvmSample Silverlight project, add the following Silverlight Page file to the Views folder. Also, add a reference to System.Windows.Controls.Data.

854816/ProductList.png

The following is the XAML code required to create the control, along with a semi-transparent loading progress bar:

854816/ProductListXaml.png

Now you need to add a link to this new Product List page to MainPage.xaml right underneath Divider1:

854816/ProductsListLink.png

Next add a ChildWindow product detail page with the following XAML code that also has an OK button and Cancel button:

854816/ProductDetailXaml.png

Also, add this to the code-behind file along with a using MvvmSample.Services:

C#
private Product _product;
public Product Product
{
    get { return _product; }
    set { _product = value; DataContext = _product; }
}

Since you now have the ProductDetail ChildWindow, go back to ProductList.xaml.cs to add this code:

C#
public partial class ProductList : Page
{
    public ProductList()
    {
        InitializeComponent();

        NavigationCacheMode = NavigationCacheMode.Enabled;

        EditProduct.Click += new RoutedEventHandler(EditProduct_Click);
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        if (ProductsGrid.ItemsSource == null)
        {
            LoadingProgress.Visibility = Visibility.Visible;

            var client = new ProductsServiceClient();

            client.GetProductsCompleted += (s, ea) =>
            {
                LoadingProgress.Visibility = Visibility.Collapsed;

                ProductsGrid.ItemsSource = ea.Result;
            };

            client.GetProductsAsync();
        }
    }

    private ProductDetail _productDetail = new ProductDetail();
    void EditProduct_Click(object sender, RoutedEventArgs e)
    {
        _productDetail.Product = ProductsGrid.SelectedItem as Product;
        _productDetail.Show();
    }
}

The above code has the check ProductsGrid.ItemsSource == null because pages are cached so we don't want to make web service calls that are not necessary.

Also, it has an event handler for editing the Product right now--later, when we get more into the MVVM pattern, I'll show you different ways to implement this same functionality.

Also, notice how it is necessary to explicitly set the LoadingProgress bar to Visible when making the web service call and Collapsed after the results have been returned.

Next, add the ProductDetails code behind, where it is important to add the Product data member and set the DataContext. The Product data member is necessary for binding to the UI:

C#
public partial class ProductDetail : ChildWindow
{
    public ProductDetail()
    {
        InitializeComponent();
    }

    private Product _product;
    public Product Product
    {
        get { return _product; }
        set { _product = value; DataContext = _product; }
    }

    private void OKButton_Click(object sender, RoutedEventArgs e)
    {
        this.DialogResult = true;
    }

    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        this.DialogResult = false;
    }
}

The main problem with putting so much in the code-behind is that it cannot be easily tested or reused here. Also, if you keep the business logic separate from the UI, it is easier to design the UI. If you keep them separate, then you can have a UI designer work on the UI and a developer work on the business logic. That said, there is nothing wrong with having lots of code in the code-behind as long as it is focused on view-logic.

It is better to have the code-behind of a XAML file be focused only on interacting with the user, and have other business logic in the ViewModel. Please note that the ViewModel does not know anything about the structure of the View. You may have heard of additional patterns such as Inversion of Control, Dependency Injection, the Command pattern, the Locator pattern, the Repository pattern, which are useful but knowledge of these patterns is not necessary for MVVM.

3. A Simple MVVM Implementation

Now let's move some of the code from the code-behind files into the ViewModel by creating a base class which provides an INotifyPropertyChanged implementation. INotifyPropertyChanged is required whenever other classes may bound to your class.

Add a ViewModel folder to your Silverlight application with this code:

C#
using System.ComponentModel;

namespace MvvmSample.ViewModels
{
    public abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
    }
}        

Please note that this implementation is not the best for thread-safety and the dispatcher. At this time, I am trying to keep everything as simple as possible. For more details, please see here.

Now create a derived class view model to explicitly work with the products:

C#
public class ProductListViewModel : ViewModel
{
    ProductsServiceClient _client = new ProductsServiceClient();

    private Product _selectedProduct;
    public Product SelectedProduct
    {
        get { return _selectedProduct; }
        set
        {
            _selectedProduct = value;
            NotifyPropertyChanged("SelectedProduct");
        }
    }

    private ObservableCollection<Product> _products;
    public ObservableCollection<Product> Products
    {
        get { return _products; }
        private set
        {
            _products = value;
            NotifyPropertyChanged("Products");
        }
    }

    public event EventHandler ProductsLoaded;
    public void LoadProducts()
    {
        _client.GetProductsCompleted += client_GetProductsCompleted;
        _client.GetProductsAsync();
    }

    void client_GetProductsCompleted(object sender, GetProductsCompletedEventArgs ea)
    {
        Products = ea.Result;
        OnProductsLoaded();
    }

    protected void OnProductsLoaded()
    {
        if (ProductsLoaded != null)
            ProductsLoaded(this, EventArgs.Empty);
    }

    public void SaveProduct()
    {
        _client.UpdateProduct(SelectedProduct.ProductID, SelectedProduct.Name);
    }
}

You could also perform tasks such as filtering or sorting the employee collection in the above view model, which keeps these activities away from the UI.

Then you need to update the XAML code in ProductList.xaml.cs, which binds the data grid to the Products collection. Notice that the SelectedProduct has the binding mode set to TwoWay, so that the DataGrid and code behind may update this value:

854816/ProductListUpdate.png

Now, you also need to update the ProductList code behind to use the new view model:

C#
public partial class ProductList : Page
{
    private ProductListViewModel _viewModel = null;

    public ProductList()
    {
        InitializeComponent();

        NavigationCacheMode = NavigationCacheMode.Enabled;

        EditProduct.Click += new RoutedEventHandler(EditProduct_Click);
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        if (_viewModel == null)
        {
            _viewModel = new ProductListViewModel();
            _viewModel.ProductsLoaded += (s, ea) =>
            {
                LoadingProgress.Visibility = Visibility.Collapsed;
            };

            DataContext = _viewModel;

            LoadingProgress.Visibility = Visibility.Visible;

            _viewModel.LoadProducts();
        }
    }

    private ProductDetail _productDetail = new ProductDetail();
    void EditProduct_Click(object sender, RoutedEventArgs e)
    {
        _productDetail.Product = ProductsGrid.SelectedItem as Product;
        _productDetail.Show();
    }

    private void SaveProduct_Click(object sender, RoutedEventArgs e)
    {
        if (_viewModel == null)
        {
            _viewModel = new ProductListViewModel();
        }
        _viewModel.SaveProduct();
    }
}

Now build and run your project--you should see the list of products in the data grid. Also, if you edit a product name and click Save, you should notice that the change persists after restarting the application.

Separating our Service Calls

One of the benefits you have now realized is that the DataGrid is now no longer tied to the UI, so you could easily replace that with a ListView without needing to change any of the UI code-behind.

If your Silverlight application ever stops using a web service to get the data and instead uses, say, a local file, or data in isolated storage, then you would need to change every one of your ViewModel classes, so let's move the web service call out of the view models.

Add a new Services folder to your Silverlight application, and add the following class:

C#
using System;
using System.Collections.ObjectModel;

namespace MvvmSample.Services
{
    public class ProductDataService
    {
        ProductsServiceClient _client = new ProductsServiceClient();
        private ObservableCollection<Product> _products;
        public ObservableCollection<Product> Products
        {
            get { return _products; }
            private set { _products = value; }
        }

        private bool _areProductsLoaded;
        public bool AreProductsLoaded
        {
            get { return _areProductsLoaded; }
            private set { _areProductsLoaded = value; }
        }

        public void LoadProducts()
        {
            AreProductsLoaded = false;
            _client.GetProductsCompleted += client_GetProductsCompleted;
            _client.GetProductsAsync();
        }

        void client_GetProductsCompleted
             (object sender, GetProductsCompletedEventArgs ea)
        {
            Products = ea.Result;
            AreProductsLoaded = true;
            OnProductsLoaded();
        }

        public event EventHandler ProductsLoaded;
        protected void OnProductsLoaded()
        {
            if (ProductsLoaded != null)
                ProductsLoaded(null, EventArgs.Empty);
        }
        
        public void UpdateProduct(int id, string name)
        {
            _client.UpdateProductNameAsync(id, name);
        }        
    }
}    

The AreProductsLoaded is used if your data is cached on the client. Now make the following changes to your ViewModel class:

C#
using System.Collections.ObjectModel;
using System.ComponentModel;
using MvvmSample.Services;
using System;

namespace MvvmSample.ViewModels
{
    public class ProductListViewModel : ViewModel
    {
        private Product _selectedProduct;
        public Product SelectedProduct
        {
            get { return _selectedProduct; }
            set
            {
                _selectedProduct = value;
                NotifyPropertyChanged("SelectedProduct");
            }
        }

        private ObservableCollection<Product> _products;
        public ObservableCollection<Product> Products
        {
            get { return _products; }
            private set
            {
                _products = value;
                NotifyPropertyChanged("Products");
            }
        }

        private ProductDataService _dataService = new ProductDataService();
        public event EventHandler ProductsLoaded;
        public void LoadProducts()
        {
            if (_client.AreProductsLoaded)
            {
                Products = _client.Products;
                OnProductsLoaded();
            }
            else
            {
                _dataService.ProductsLoaded += (s, e) =>
                {
                    Products = _client.Products;
                    OnProductsLoaded();
                };

                _dataService.LoadProducts();
            }
        }

        protected void OnProductsLoaded()
        {
            if (ProductsLoaded != null)
                ProductsLoaded(this, EventArgs.Empty);
        }
        
        public void SaveProduct()
        {
            _dataService.UpdateProduct(SelectedProduct.ProductID, SelectedProduct.Name);
        }        
    }
}    

Now your view model only has to deal with data and functionality related to the view.

Separate Out More Code from the UI Using Commands

Events and event handlers cause coupling between the UI and the code-behind, so one alternative is to implement ICommand.

With commands, instead of responding to a button click event, you bind the command to the button and allow the button to execute that command directly.

Add a new class called RelayCommand to your ViewModel folder:

C#
using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MvvmSample.ViewModels
{
    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;        

        #endregion 

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;           
        }

        #endregion

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged;
        public void OnCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion
    }
}    

Then, add the command-related source code to ProductListViewModel.cs:

C#
public bool CanUpdateProductName
{
    get { return SelectedProduct != null; }
}

private RelayCommand _updateProductNameCommand = null;
public RelayCommand UpdateProductNameCommand
{
    get
    {
        if (_updateProductNameCommand == null)
        {
            _updateProductNameCommand = new RelayCommand
            (
                p => SaveProduct(),
                p => CanUpdateProductName
            );
        }

        return _updateProductNameCommand;
    }
}

In the above code, the p => indicates it is an anonymous delegate where the delegate takes a single parameter p.

Now, add this code to include the call to OnCanExecuteChanged:

C#
private Product _selectedProduct;
public Product SelectedProduct
{
    get { return _selectedProduct; }
    set
    {
        _selectedProduct = value;
        NotifyPropertyChanged("SelectedProduct");
        UpdateProductNameCommand.OnCanExecuteChanged();
    }
}

Then add Command="{Binding UpdateProductNameCommand}" to the ProductList.xaml file Save button:

854816/UpdateProductNameCommand.png

You are now able to remove the event handler from the code behind.

Build and run the application to verify that you can update a product's name and that the name persists.

Dependency Injection

Dependency Injection is a specific form of Inversion of Control (IoC). Dependency Injection is a software pattern where the control of a particular function is determined not by the algorithm that is inside the function, but instead by an object that is passed in. It is useful, for example, when you would want the software to behave in one way while in production mode and a different way while being tested.

Imagine you have an interface INotificationService and two concrete implementations of NotificationService:

C#
// 1. Production ProductionNotificationService:
 ShowMessage(string message)
 {
     MessageBox.Show(message);
 }

// 2. Test MockNotificationService:
 ShowMessage(string message)
 {
     // Do nothing
 }

 MyViewModel(INotificationService service)
 {
     service.ShowMessage("foo");
 }

IoC allows you to pass in to the above constructor based on whether your want to run unit tests or run the production version of your application. For details, please see Dependency Injection and Inversion of Control.

IoC and ViewModel Locators

Thus far, you have removed coupling between the UI and the database; however, each of the classes are still tightly coupled together, such as the View being coupled to the ViewModel, since you need to create the ViewModel in the code behind. Also, the ProductListViewModel is coupled to the ProductDataService.

When the ViewModels and services are implemented as interfaces, you can easily swap them out with different implementations. Consider the case where you are developing and don’t yet have a database, or you are designing the UI and don’t want to force the designer to write the code-behind as well.

Inversion of Control (IoC) allows developers to design their system so they don’t have to directly create any object of consequence in their code, and rather have the IoC container resolve for an object of a particular type. The IoC container then produces a test version or a production version if requested.

Add the following class to the ViewModel directory:

C#
using System.Collections.Generic;

namespace MvvmSample.ViewModels
{
    public class ViewModelLocator
    {
        private Dictionary<string, ViewModel> _viewModels = 
                           new Dictionary<string, ViewModel>();

        public ViewModelLocator()
        {
            _viewModels.Add("ProductList", new ProductListViewModel());
        }

        public ViewModel this[string viewName]
        {
            get { return _viewModels[viewName]; }
        }
    }
}

Add "ResourceDictionary Source="Assets/Resources.xaml"" and this new file Resources.xaml to App.xaml so that it can be used for binding:

854816/Resources.png

Now add this code to the Product List XAML:

C#
DataContext="{Binding [ProductList],Source={StaticResource ViewModelLocator}}"

Now add this code to the Product List code behind:

C#
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    _viewModel = DataContext as ProductListViewModel;

    _viewModel.ProductsLoaded += (s, ea) =>
    {
        LoadingProgress.Visibility = Visibility.Collapsed;
    };

    DataContext = _viewModel;

    LoadingProgress.Visibility = Visibility.Visible;

    _viewModel.LoadProducts();
}

Now that we have completed the more entity-type ViewModel, the front end is more separated from the back end, so it is easier to change either the front end or back end without affecting other parts of the program, not to mention making it easier to test.

4. Adding Exception Handling Capabilities

In Silverlight, you can enable the WCF SOAP fault programming model, so that the back-end service can send messages to the client-side, instead of just displaying an HTTP 500 response code.

To enable these SOAP faults, add the following class to the same location in your *.Web project as your WCF service:

C#
// This is an auto-generated file to enable WCF faults to reach Silverlight clients.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WcfEntitiesSample.Web.Services
{
    public class SilverlightFaultBehavior : Attribute, IServiceBehavior
    {
        private class SilverlightFaultEndpointBehavior : IEndpointBehavior
        {
            public void AddBindingParameters
            (ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
            {
            }

            public void ApplyClientBehavior
            (ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
            }

            public void ApplyDispatchBehavior
            (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
                endpointDispatcher.DispatchRuntime.MessageInspectors.Add
                            (new SilverlightFaultMessageInspector());
            }

            public void Validate(ServiceEndpoint endpoint)
            {
            }

            private class SilverlightFaultMessageInspector : IDispatchMessageInspector
            {
                public object AfterReceiveRequest
                (ref Message request, IClientChannel channel, 
                                      InstanceContext instanceContext)
                {
                    return null;
                }

                public void BeforeSendReply(ref Message reply, object correlationState)
                {
                    if ((reply != null) && reply.IsFault)
                    {
                        HttpResponseMessageProperty property = 
                                           new HttpResponseMessageProperty();
                        property.StatusCode = HttpStatusCode.OK;
                        reply.Properties[HttpResponseMessageProperty.Name] = property;
                    }
                }
            }
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, 
        ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, 
        BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior
            (ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
            {
                endpoint.Behaviors.Add(new SilverlightFaultEndpointBehavior());
            }
        }

        public void Validate(ServiceDescription serviceDescription, 
                             ServiceHostBase serviceHostBase)
        {
        }
    }
}    

Next, add the following class to your WcfEntitiesSample.Model.Silverlight and link it to the WcfEntitiesSample.Model.Net project:

C#
namespace WcfEntitiesSample.Model
{
    public class WcfError
    {
        public string Message { get; set; }
    }
}    

To link the file when adding a new item, click on the arrow drop-down and choose "Add As Link":

854816/LinkFile.png

Then in your svc file, add the SilverlightFaultBehavior and FaultContract attributes:

C#
[SilverlightFaultBehavior]
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode =
                    AspNetCompatibilityRequirementsMode.Allowed)]
public class AdventureWorksService : IAdventureWorksService
{
    [OperationContract]
    [SilverlightFaultBehavior]
    [FaultContract(typeof(WcfError))]
    public Product GetProductByID(int id)
    {
        var mgr = new ProductManager();
        return mgr.GetProductById(id);
    }
}

Now to actually view the error in your Silverlight project, update your MainPage.xaml.cs source code to include the FaultException details along with using WcfEntitiesSample.Model:

C#
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Windows;
using System.Windows.Controls;
using WcfEntitiesSample.Model;
using WcfEntitiesSample.ServiceRefs;

namespace WcfEntitiesSample
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            // Required to initialize variables
            InitializeComponent();
        }

        private void btnSearch_Click(object sender, RoutedEventArgs e)
        {
            int searchId;

            if (int.TryParse(txtID.Text, out searchId))
            {
                var client = new AdventureWorksServiceClient();

                client.GetProductByIDCompleted += (s, ea) =>
                {
                    if (ea.Error != null)
                    {
                        FaultException<WcfError> fault = 
                             ea.Error as FaultException<WcfError>;

                        if (fault != null)
                        {
                            MessageBox.Show(fault.Detail.Message);
                        }
                        else
                        {
                            MessageBox.Show(ea.Error.Message);
                        }
                    }
                    DataContext = ea.Result;
                    Debug.WriteLine("ea.Result.ToString() = ", ea.Result.ToString());
                };

                client.GetProductByIDAsync(searchId);
                Debug.WriteLine("client.ToString() = ", client.ToString());
            }
            else
            {
                MessageBox.Show("Invalid customer ID. Please enter a number.");
            }
        }
    }
}

5. Testing

Requires the latest Silverlight Toolkit which can be obtained from Codeplex though the best way to get it probably is using NuGet with this command:

PM> Install-Package SilverlightToolkit-Testing

If you experience any difficulties, then simply manually add the Microsoft.Silverlight.Testing.dll and Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight.dll files to your project that are included in the sample project zip file.

After installing this, you will now have a new Silverlight Unit Test project template.

Add a link in your unit testing project to the ServiceReferences.ClientConfig file.

Create a new class called ProductListViewModelTests, and add the following code:

C#
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MvvmSample.ViewModels; 
namespace MvvmApplication.SilverlightTests 
{ 
    [TestClass] 
    public class ProductListViewModelTests 
    { 
        [TestMethod] 
        public void SelectedProductCanBeSetAndRetrieved() 
        { 
            ProductListViewModel vm = new ProductListViewModel(); 
            MvvmSample.Services.Product product = new MvvmSample.Services.Product(); 
            vm.SelectedProduct = product; 
            Assert.ReferenceEquals(product, vm.SelectedProduct); 
        } 
    } 
} 

You also need to set your startup page to MvvmSample.TestsTestPage.aspx in the *.Web project. When you run the Silverlight tests, you should see something like this:

Image 15

Also, to test your ProductManager class, add a standard .NET Unit Testing library and add the following code:

C#
using System; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using WcfEntitiesSample.Model; 
namespace UnitTests 
{ 
    [TestClass] 
    public class TestProductManager 
    { [TestMethod] 
        public void TestGetProducts() 
        { 
            var mgr = new ProductManager(); 
            var products = mgr.GetProducts(); 
            Assert.IsTrue(products.Count > 0); 
        } 
    } 
} 

If your application has an architecture that has been designed well, then your tests of the view model and manager classes should cover most of the application logic.

History

  • 15th January, 2015: Added section on testing
  • 5th February, 2015: Added further explanation and example on Inversion of Control

References

License

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