Read Chapter%207%20-%20MVVM%20with%20SL%20Business%20template.pdf text version

MVVM with Silverlight Business Template

In Chapters 1 to 6 we looked at the RIA data services where we followed the pattern of a User Interface(UI) that is associated with some code-behind that is responsible for loading in data, and implementing logic to handle user input and interaction in the form of event handlers. In other words, there is no decoupling of the data and the UI, but of course these are simple samples. However as you move to developing more robust applications, this decoupling becomes much important to avoid redoing the UI each time that we change the backend data provider. I have described decoupling the data access on the back-end in Chapter 5 ( POCO used to abstract our data access) and this article is designed to show how the same can be accomplished in a Silverlight client side application. This decoupling will also allow us to enable unit testing for our UI. The MVVM pattern that we are studying stands for Model-View-View-Model and if you are familiar with MVC then you might wonder what is going on here as it really seems like the same thing and you would be right but there are subtle differences. Model­view­controller (MVC) is an architectural pattern whose successful use isolates business logic from user interface considerations, resulting in an application where it is easier to modify either the visual appearance of the application or the underlying business rules without affecting the other. In MVC, the model represents the information (the data) of the application i.e. my Customer table in Northwind and the Linq to Sql class that will access the data; the view corresponds to elements of the user interface such as text, checkbox items, and so forth; and the controller manages the communication of data and the business rules used to manipulate the data to and from the model. Thus we get the decoupling that we desire but this pattern becomes harder to follow in web applications except of course for ASP.NET MVC which enforces the pattern. However many Silverlight applications and WPF are adopting the MVVM pattern that allows us to achieve the decoupling or as it is also referred to as "separation of concerns" To this point our solutions look like:

With the ViewModel pattern the data being presented is implemented as properties on the ViewModel that the View consumes via data-binding. Secondly, most of the backing logic and operations are implemented as methods on the ViewModel that the view invokes via commanding which is new in Silverlight 4. The key constraint is that the ViewModel is not dependent on View concepts such as choice of controls and can feed multiple views and the two can be changed independently as long as the contracts remain in place. Things now look like:

Of course the downside is that we are trading off flexibility for complexity and many developers balk at using these patterns even senior ones but remember the development of a product is only small part of the life cycle of a production product and is dwarfed by the time spent in maintenance and extending. So let's look at the code but first examining what is wrong with our Simple example where the View is bound directly to the properties of the Category objects:

<data:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" > <data:DataGrid.Columns> <data:DataGridTextColumn Header="Id" IsReadOnly="True" Binding="{Binding CategoryID}" /> <data:DataGridTextColumn Header="Name" Binding="{Binding CategoryName}" /> <data:DataGridTextColumn Header="Description" Binding="{Binding Description}" /> </data:DataGrid.Columns> </data:DataGrid> So we are tightly coupling the View to our Model with not bad for quick and dirty UIs but does not give us the flexibility we want for our production applications. Especially as we shall see later this leads us to an architecture that is easily unit tested, a goal that should be near the top of your list. In the end we want to have an architecture that looks like:

However in the first version we dispense with the ICategory interface and put the data access code in the ViewModel as I like to take small steps. I based my solution on Laurent Bugnion's MVVM Light framework with is available at . This defines much of the plumbing that we need including the Silverlight 4 Command tooling that we will need. John Papa also has a good article and solution available at . I found Laurent's to be more complete so I used it. Now we again start with the Silverlight Business template as we will use WCF RIA for the data access. I then added the references that I need from MVM light:

Add these from MVVM light

This time I was lazy and just used the Home page I first I added a ViewModels folder to my Silverlight project and add the CatViewModelRelay class that looks like:

public class CatViewModelRelay : ViewModelBase { public RelayCommand SaveCommand { get; set; } public NWDomainContext dc = null; public IEnumerable Cats { get; set; } public CatViewModelRelay() Defined in ViewModelbase and { set if we are in designer so no if (IsInDesignModeStatic) I/O if true return;

Abstract class in MVVM light that defines functions we need like INotifyPropertyChanged

dc = new NWDomainContext(); Use WCF RIA to load the categories LoadOperation loadop = dc.Load<Category>(dc.GetCategoriesQuery()); loadop.Completed += new EventHandler(loadop_Completed); Cats = dc.Categories; SaveCommand = new RelayCommand( () => First lambda is method to be { executed dc.SubmitChanges(); SaveCommand.RaiseCanExecuteChanged(); }, 2nd lambda determines if command is valid, in this case save is only () => dc.HasChanges); }

enabled if changes are made to DataGrid

void loadop_Completed(object sender, EventArgs e) { foreach (Category cat in dc.Categories) If there are user changes to a cat.PropertyChanged += (s, e1) => category entity then we want the SaveCommand.RaiseCanExecuteChanged();

Save to be enabled

} }

Not much code but I am leveraging the base functionality that Laurent is providing. Note that all the details on Laurent's solution are on his blog. The only tricky part was wiring up the PropertyChanged event on the Category entities as this will result in the second Lambda being executed. So now we need to modify the Home.XAML file to bind to our ViewModel and not the Home.XAML.CS so we add few lines to the XAML:

<navigation:Page xmlns:data="clrnamespace:System.Windows.Controls;assembly=System.Windows.Contro ls.Data" x:Class="MVVMStart.Home" xmlns="" xmlns:x="" xmlns:d="" xmlns:mc="" Insert xmlns:vm="clr-namespace:MVVMStart.ViewModels" x:Name="HomePage" xmlns:navigation="clrnamespace:System.Windows.Controls;assembly=System.Windows.Contro ls.Navigation" mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480" Style="{StaticResource PageStyle}"> Define our ViewModel class that we <navigation:Page.Resources> will bind to <vm:CatViewModelRelay x:Key="MYVM"/> </navigation:Page.Resources> <Grid x:Name="LayoutRoot" DataContext="{StaticResource MYVM}">

Our ViewModel is now the dataContext so all binding occurs against it

Properties in ViewModel

<data:DataGrid x:Name="MyGrid" ItemsSource="{Binding Cats}" AutoGenerateColumns="False" > <data:DataGrid.Columns>

<data:DataGridTextColumn Header="Name" Binding="{Binding CategoryName}" /> <data:DataGridTextColumn Header="Description" Binding="{Binding Description}" /> </data:DataGrid.Columns> </data:DataGrid> <Button Content="Save" Height="23" Name="button1" Width="75" Command="{Binding SaveCommand}" CommandParameter="{Binding Category}"></Button>

We are using SL4 Commanding as there is no click handler and SaveCommand is defined in ViewModel

Now there is no code in our Home.xaml.cs file so we have successfully decoupled the View but just to be complete there are times when you want the code behind page to have knowledge of the ViewModel (not the other way around). Sometimes you have logic that is solely UX functionality and I think it over zealous to place this in the ViewModel. Anyways it is a question of how regimented your architecture is and I am not going there. One way to add the ViewModel reference to the View is:

using MVVMStart.ViewModels; CatViewModelRelay ViewModel { get { return (CatViewModelRelay)LayoutRoot.DataContext; } }

Adding an Interface for Data Access

This solution is moving us in the right direction but there a couple of other factors that we need to bring into the picture and that is Unit Testing and mock data for tools like Blend. MVVM can help us with both of these, of course Unit Testing is a key practice in the software principals that you need to follow. Microsoft now provides the Silverlight Unit Test Application that is available at Silverlight Unit testing. Blend is the UX tool that will let your designers make your application sizzle but the designers often need mock data and MVVM can help here also. I believe it is essential that your application is Blendable as it is great UX that sells software. To accomplish these goals we are now going to move our data access (Model) behind an interface. The first thing we do is add a new Folder called DataAccess to the Models folder in the Silverlight project. In here we add two classes, the first is IDataServices that will contain the methods that we are abstracting: public interface IDataServices { void GetCategories(Action<IEnumerable<Category>> callback); void SaveChanges(); bool hasChanges(); bool IfMonthHas30Days(string month); } These are straight forward except for the GetCategories that will accept as it parameter a callback that will return the categories returned by the data layer. This just makes it easier to deal with the asynchronous nature of Silverlight.

Now I add a class called WCFRiaDataAccess that will implement this simple interface as follows: public class WCFRiaDataAccess : IDataServices { public CatDomainService1 dc = null; IEnumerable Cats { get; set; } public void GetCategories(Action<IEnumerable<Category>> callback) { dc = new CatDomainService1(); LoadOperation loadop = dc.Load<Category>(dc.GetCategoriesQuery()); loadop.Completed += (sender, e) => callback(dc.Categories); } public void SaveChanges() { dc.SubmitChanges(); } public bool hasChanges() { return dc.HasChanges; }

So we have abstracted our data access and this will let us make the following changes to the ViewModel code: IDataServices ds = null; public CatViewModelRelay() { if (IsInDesignModeStatic) return; ds = new WCFRiaDataAccess() as IDataServices; StartVMOperations(); } public CatViewModelRelay(IDataServices ds) { this.ds = ds; StartVMOperations(); } void StartVMOperations() { ds.GetCategories(CatgoriesRetrieved);

Methods in the class that SaveCommand = new RelayCommand( implements IDataServices are () => consumed by the ViewModel { //first lamba is method to be executed ds.SaveChanges(); // Cause the command to be reevaluated. SaveCommand.RaiseCanExecuteChanged(); }, () => ds.hasChanges()); //Save will only be enabled if there are changes

By default we use a WCF RIA for the data access

However we can override the provider by passing the Interface in a constructor

} void CatgoriesRetrieved(IEnumerable<Category> cats) { Cats = cats; foreach (Category cat in Cats) cat.PropertyChanged += (s, e1) => SaveCommand.RaiseCanExecuteChanged();

So now we can use this interface to add our Unit test for the ViewModel as we add a Silverlight Unit test project to our solution that I call MVVMwithInterface2.Tests. Then I add a new class called MockCats that will provide mock data for unit testing the viewmodel, it should be noted that the same approach will be used for providing mock data to Blend. As you can see below I am just providing a few Category objects to the viewmodel at this point: public class MockCats : IDataServices { List<Category> cats = null;

public void GetCategories(Action<System.Collections.Generic.IEnumerable<Web.Category>> callback) { cats = new List<Category>() { new Category{ CategoryID = 1, CategoryName = "MyCat", Description = "This is an item"}, new Category{ CategoryID = 2, CategoryName = "MyCa2t", Description = "This is an item2"}, new Category{ CategoryID = 3, CategoryName = "MyCat3", Description = "This is an item3"} }; callback(cats); } public void SaveChanges() { throw new NotImplementedException(); } public bool hasChanges() { throw new NotImplementedException(); } public bool IfMonthHas30Days(string month) { throw new NotImplementedException(); } #endregion }

So with this class in place we can write unit tests against the ViewModel like : [TestClass] public class Tests { [TestMethod] public void TestMethod1() { IDataServices mock = new MockCats() as IDataServices; CatViewModelRelay vm = new CatViewModelRelay(mock); IEnumerable<Category> cats = vm.Cats;

Not a great test but you should get the idea

Assert.IsTrue(vm.Cats.Count() == 3, "Error we must return 3 categories"); } }


11 pages

Report File (DMCA)

Our content is added by our users. We aim to remove reported files within 1 working day. Please use this link to notify us:

Report this file as copyright or inappropriate