Freigeben über


Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 25: ViewModel

ViewModel (or Model-View-ViewModel) is an emerging pattern in the WPF, Silverlight space which enables a separation of concerns similar to that of the MVC pattern that is popular on for stateless web apps today (for example: ASP.NET MVC).  John Gossman was the first one I heard talk about the pattern from his days working on Expression Blend.  Of course, this was is simply an application of Martin Fowler’s Presentation Model pattern. 

In this example, I will take our ever popular SuperEmployees application and re-write it with the ViewModel pattern.   As with any emerging patterns, there are lots of variations, all with their strengths and weaknesses.. I have picked an approach that I felt was best as an introduction.

You can see the full series here.

The demo requires (all 100% free and always free):

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview
  4. (Optional) Silverlight Unit Testing Framework

Check out the live site Also, download the full demo files

For this example, we are going to focus exclusively on the client project (MyApp and MyApp.Tests)… check out the previous posts for more information about the server side of this app.

 

Orientation

Model (SuperEmployeeDomainContext in MyApp.Web.g.cs) – Responsible for data access and business logic.
View  (home.xaml) – Responsible for the purely UI elements
ViewModel (SuperEmployeesViewModel.cs) – Specialization of the Model that the View can use for data-binding. 

ViewModel Pattern

See more ViewModel Pattern in Silverlight at Nikhil’s blog. 

There are some other interesting bits in the “PatternsFramework” folder.  They contain some useful helper classes that may be applicable to other ViewModel+RIA Services applications.

ViewModelBase – From Nikhil’s ViewModel example
PagedCollectionView– Adds paging support (IPagedCollectionView) in a fairly standard way.
EntityCollectionView – From Jeff Handley blog post handles most of the interfaces needed for binding, and of course works super well with RIA Services. 
PagedEntityCollectionView – Added paging support..  This gives us most of what DomainDataSource provides, but is very ViewModel friendly. 

 

Loading Data

Let’s start with just getting some basic data into the application.  I am going to do all by databinding against my SuperEmployeesViewModel, so I am going to set it up as the DataContext of the page. 

 

 public class SuperEmployeesViewModel : PagedViewModelBase {}
 

and then from home.xaml

 <navigation:Page.DataContext>
    <AppViewModel:SuperEmployeesViewModel />
</navigation:Page.DataContext>

Now, we are ready to start..  As you recall from previous posts, the app is very simple master-details setup.

image

Let’s start by getting the DataGrid and DataForm bindings wired up…

    1: <data:DataGrid x:Name="dataGrid1" Height="380" Width="380" 
    2:                IsReadOnly="True" AutoGenerateColumns="False" 
    3:                HorizontalAlignment="Left" 
    4:                SelectedItem="{Binding SelectedSuperEmployee, Mode=TwoWay}"
    5:                HorizontalScrollBarVisibility="Disabled"
    6:                ItemsSource="{Binding SuperEmployees}"                                            
    7:            >
    1: <dataControls:DataForm x:Name="dataForm1" Height="393" Width="331"
    2:        VerticalAlignment="Top"       
    3:        Header="Product Details"
    4:        CurrentItem="{Binding SelectedSuperEmployee}"    
    5:         HorizontalAlignment="Left" >

Notice in DataGrid, line 6 we are binding to the SuperEmployees property on the ViewModel.  we will look at how that is defined next.  Then in line 4, we twoway bind the SelectedSuperEmployee property.  This means that the DataGrid will set that property when the user selects an item.  Finally in line 3 on DataForm, we bind to that same property. 

From the SuperEmployeesViewModel.cs, we see the SuperEmployees and SelectedSuperEmployee properties…  Notice we raise the property change notifications such that the UI can update when these values change.

    1: PagedEntityCollectionView<SuperEmployee> _employees;
    2: public PagedEntityCollectionView<SuperEmployee> SuperEmployees
    3: {
    4:     get { return _employees; }
    5:     set
    6:     {
    7:         if (_employees != value)
    8:         {
    9:             _employees = value;
   10:             RaisePropertyChanged(SuperEmployeesChangedEventArgs);
   11:         }
   12:     }
   13: }
   14:  
   15: private SuperEmployee _selectedSuperEmployee;
   16: public SuperEmployee SelectedSuperEmployee
   17: {
   18:     get { return _selectedSuperEmployee; }
   19:     set
   20:     {
   21:         if (SelectedSuperEmployee != value)
   22:         {
   23:             SuperEmployees.MoveCurrentTo(value);
   24:             _selectedSuperEmployee = value; 
   25:             RaisePropertyChanged(SelectedSuperEmployeeChangedEventArgs);
   26:         }
   27:     }
   28: }
   29:  

Ok, that is the wiring, but how did _employees get its value set in the first place?  How is the data actually loaded?

Well, check out the SuperEmployeesViewModel constructor.

    1: public SuperEmployeesViewModel()
    2: {
    3:     _superEmployeeContext = new SuperEmployeeDomainContext();
    4:     SuperEmployees = new PagedEntityCollectionView<SuperEmployee>(
    5:                                _superEmployeeContext.SuperEmployees, this);
    6:    
    7: }

We see the SuperEmployees is actually a PagedEntityCollectionView..  We pass this as the IPagedCollectionView, so we get called back on that when data loading is needed (for example, when I move to page 1).  The base PageViewModelhandles hands all the plumbing there, but we still need to handling loading the data via our implementation of the LoadData() method. 

    1: public override void LoadData ()
    2: {
    3:     if (IsLoading || _superEmployeeContext == null)
    4:     {
    5:         return;
    6:     }
    7:  
    8:     IsLoading = true;
    9:  
   10:     _superEmployeeContext.SuperEmployees.Clear();
   11:     var q = _superEmployeeContext.GetSuperEmployeesQuery();
   12:  
   13:     _superEmployeeContext.Load(q, OnSuperEmployeesLoaded, null);
   14: }

You can see this is fairly simple, we just clear the list of what we may have already downloaded, then loads more data.  Notice we are not actually handling paging here yet, we will get to that is a bit. 

image

 

Filtering

Now, we have that nice Origins filter… let’s see how we wire this up such that we only return the entities that have a certain origin.  Now it is important that we don’t want to return all the entities and do this filtering on the client.. that would waste way to much bandwidth.  We also don’t want to do the filtering on the middle tier (web server) as that could still flood the database.. instead we want to do this filtering all the way down in the database.  We can do that via the magic of Linq query composition.  We are going to form a Linq query on the client, send it to the web server, who will simply pass it along (via Entity Framework in this example) to the database. 

First, in the Home.xaml view, we wireup the databinding:

    1: <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
    2:     <TextBlock Text="Origin: " />
    3:     <TextBox x:Name="originFilterBox" Width="338" Height="30"
    4:              Text="{Binding OriginFilterText, Mode=TwoWay}"></TextBox>
    5: </StackPanel>

Notice in line 4, we are doing the binding to the OriginFilterText property on our ViewModel. Let’s take a look at what that looks like.

    1: string _originFilterText;
    2: public string OriginFilterText
    3: {
    4:     get { return _originFilterText; }
    5:     set
    6:     {
    7:         if (_originFilterText != value)
    8:         {
    9:             _originFilterText = value;
   10:             RaisePropertyChanged(OriginFilterTextChangedEventArgs);
   11:  
   12:             PageIndex = 0;
   13:             LoadData();
   14:         }
   15:     }
   16: }

Notice whenever the filter text is changed, we need to read load the data… But as you recall from the LoadData method above, it simply loaded all the data.. how do we wire it up such that it loads just the data we matching this filter? 

    1: public override void LoadData ()
    2: {
    3:     if (IsLoading || _superEmployeeContext == null)
    4:     {
    5:         return;
    6:     }
    7:  
    8:     IsLoading = true;
    9:  
   10:     _superEmployeeContext.SuperEmployees.Clear();
   11:     var q = _superEmployeeContext.GetSuperEmployeesQuery();
   12:  
   13:     if (!String.IsNullOrEmpty(OriginFilterText))
   14:     {
   15:         q = q.Where(emp => emp.Origin.StartsWith(OriginFilterText));
   16:     }
   17:  
   18:     _superEmployeeContext.Load(q, OnSuperEmployeesLoaded, null);
   19: }
   20:  

Notice, we added lines 13-16.. we are simply adding a clause to the query…  This clause is serialized, sent to the server where it is interpreted by the DAL (EF in this case) and executed on the database. 

For the deep linking code, we need to filter on employeeID that we get from the URL, can you see how easy it would be to add in a filter by employeeID?  Check out lines 13-16.

    1: public override void LoadData ()
    2: {
    3:     if (IsLoading || _superEmployeeContext == null)
    4:     {
    5:         return;
    6:     }
    7:  
    8:     IsLoading = true;
    9:  
   10:     _superEmployeeContext.SuperEmployees.Clear();
   11:     var q = _superEmployeeContext.GetSuperEmployeesQuery();
   12:  
   13:     if (!String.IsNullOrEmpty(EmployeeIDFilter))
   14:     {
   15:         q = q.Where(emp => emp.EmployeeID == Convert.ToInt32(EmployeeIDFilter));
   16:     }
   17:  
   18:     if (!String.IsNullOrEmpty(OriginFilterText))
   19:     {
   20:         q = q.Where(emp => emp.Origin.StartsWith(OriginFilterText));
   21:     }
   22:  
   23:     _superEmployeeContext.Load(q, OnSuperEmployeesLoaded, null);
   24: }

image

You can see, we simply add another where clause that follows the same pattern. 

 

Paging

Paging is pretty much that same as filtering we just looked at.  We bind some UI controls to a property on the ViewModel, customize the load query based on that property.   In this case the UI is a DataPager and the property is the CurrentPage.

First, we need to give a PageSize (number of entities to  load at one time).   I wanted this customizable by a designer, so a made it a property in the View. 

 

    1: <navigation:Page.DataContext>
    2:     <AppViewModel:SuperEmployeesViewModel PageSize="13" />
    3: </navigation:Page.DataContext>

Then we bind the DataPager to this value and to our SuperEmployees list. 

    1: <data:DataPager x:Name ="pager1" PageSize="{Binding PageSize}" Width="379" 
    2:                 HorizontalAlignment="Left"
    3:                 Source="{Binding SuperEmployees}" 
    4:                 Margin="0,0.2,0,0" />

I defined the PageSize property in the PagedViewModelBase because it is generic to any data… But it is pretty much as you’d expect.

    1: int pageSize;
    2: public int PageSize
    3: {
    4:     get { return pageSize; }
    5:     set
    6:     {
    7:         if (pageSize != value)
    8:         {
    9:             pageSize = value;
   10:             RaisePropertyChanged(PageSizeChangedEventArgs);
   11:         }
   12:     }
   13: }

DataPager works through the IPagedCollection interface that is defined on the PagedViewModelBase. So this base class deals with all the FirstPage, NextPage, MoveTo(page) type of functionality and simply exposes a PageIndex property. 

We can use that in our LoadData() method to do the appropriate paging code that should look familiar to anyone that has done data paging in the last 20 years. ;-)

 

    1: public override void LoadData ()
    2: {
    3:     if (IsLoading || _superEmployeeContext == null)
    4:     {
    5:         return;
    6:     }
    7:  
    8:     IsLoading = true;
    9:  
   10:     _superEmployeeContext.SuperEmployees.Clear();
   11:     var q = _superEmployeeContext.GetSuperEmployeesQuery();
   12:  
   13:     if (!String.IsNullOrEmpty(EmployeeIDFilter))
   14:     {
   15:         q = q.Where(emp => emp.EmployeeID == Convert.ToInt32(EmployeeIDFilter));
   16:     }
   17:  
   18:     if (!String.IsNullOrEmpty(OriginFilterText))
   19:     {
   20:         q = q.Where(emp => emp.Origin.StartsWith(OriginFilterText));
   21:     }
   22:  
   23:  
   24:     if (PageSize > 0)
   25:     {
   26:         q = q.Skip(PageSize * PageIndex);
   27:         q = q.Take(PageSize);
   28:     }
   29:  
   30:     _superEmployeeContext.Load(q, OnSuperEmployeesLoaded, null);
   31: }

In lines 24-28, we are adding to the query a Skip() and a Take().  First we skip over the number of entities on a page times the page we are currently on.  Then we take the next number of entities on a page.   Again, all those this eventually gets turned into TSQL code and executed on the SQL Server.

   image

 

Sorting

As you might guess, sorting follows the exact same pattern.  Some UI element in the view is bind to some property on the ViewModel which we access in the LoadData() method to customize our Linq query that is sent to the server. 

In this case DataGrid is bound to the EntityCollectionView which implements ICollectionView.SortDescriptions.  So when the DataGrid sorts it changes the SortDescription there. 

So in our DataLoad() method we just need to access the SortDescription and add to the Linq query.

    1: public override void LoadData ()
    2: {
    3:     if (IsLoading || _superEmployeeContext == null)
    4:     {
    5:         return;
    6:     }
    7:  
    8:     IsLoading = true;
    9:  
   10:     _superEmployeeContext.SuperEmployees.Clear();
   11:     var q = _superEmployeeContext.GetSuperEmployeesQuery();
   12:  
   13:     if (!String.IsNullOrEmpty(EmployeeIDFilter))
   14:     {
   15:         q = q.Where(emp => emp.EmployeeID == Convert.ToInt32(EmployeeIDFilter));
   16:     }
   17:  
   18:     if (!String.IsNullOrEmpty(OriginFilterText))
   19:     {
   20:         q = q.Where(emp => emp.Origin.StartsWith(OriginFilterText));
   21:     }
   22:  
   23:     if (SuperEmployees.SortDescriptions.Any())
   24:     {
   25:         bool isFirst = true;
   26:         foreach (SortDescription sd in SuperEmployees.SortDescriptions)
   27:         {
   28:             q = OrderBy(q, isFirst, sd.PropertyName, sd.Direction == ListSortDirection.Descending);
   29:             isFirst = false;
   30:         }
   31:     }
   32:     else
   33:     {
   34:         q = q.OrderBy(emp => emp.EmployeeID);
   35:     }
   36:  
   37:     if (PageSize > 0)
   38:     {
   39:         q = q.Skip(PageSize * PageIndex);
   40:         q = q.Take(PageSize);
   41:     }
   42:  
   43:     _superEmployeeContext.Load(q, OnSuperEmployeesLoaded, null);
   44: }

Check out lines 23-35.  Here we are adding OrderBy to the linq query via a little helper method.  

    1: private EntityQuery<SuperEmployee> OrderBy(EntityQuery<SuperEmployee> q, bool isFirst, string propertyName, bool descending)
    2: {
    3:     Expression<Func<SuperEmployee, object>> sortExpression;
    4:  
    5:     switch (propertyName)
    6:     {
    7:         case "Name":
    8:             sortExpression = emp => emp.Name;
    9:             break;
   10:         case "EmployeeID":
   11:             sortExpression = emp => emp.EmployeeID;
   12:             break;
   13:         case "Origin":
   14:             sortExpression = emp => emp.Origin;
   15:             break;
   16:         default:
   17:             sortExpression = emp => emp.EmployeeID;
   18:             break;
   19:     }
   20:  
   21:     if (isFirst)
   22:     {
   23:         if (descending)
   24:             return q.OrderByDescending(sortExpression);
   25:         
   26:         return q.OrderBy(sortExpression);
   27:     }
   28:     else
   29:     {
   30:         if (!descending)
   31:             return q.ThenByDescending(sortExpression);
   32:         
   33:         return q.ThenBy(sortExpression);
   34:     }
   35: }

This helper method forms the correct sorting expression based on the a propertyname and a order..   

image

 

Interacting with the View

One of the interesting aspects of how the ViewModel pattern comes together is how the ViewModel can interact with the View.  So far we have looked at several examples of the View setting properties on the ViewModel and the view databinding to values on the ViewModel, but we have not yet seen how the ViewModel can do things like raise UI. 

A good example of that is how I refactored the ExportToExcel functionality. 

Let’s start at the view.. As you maybe seen, there is an Export to Excel button..

    1: <Button Content="Export to Excel" 
    2:         Width="105" Height="28"
    3:         Margin="5,0,0,0" HorizontalAlignment="Left"
    4:         Click="ExportToExcel_Click" ></Button>

Notice the click is handled by code behind, rather than the ViewModel.  This is because the logic is very View specific (raising a FileOpenDialog). 

    1: private void ExportToExcel_Click(object sender, RoutedEventArgs e)
    2: {
    3:     var dialog = new SaveFileDialog();
    4:  
    5:     dialog.DefaultExt = "*.xml";
    6:     dialog.Filter = "Excel Xml (*.xml)|*.xml|All files (*.*)|*.*";
    7:  
    8:     if (dialog.ShowDialog() == false) return;
    9:  
   10:     using (var fileStream = dialog.OpenFile())
   11:     {
   12:         ViewModel.ExportToExcel(fileStream);
   13:     }
   14: }

Then, in line 12, there is some actual logic that we might want to reuse or test separate, so we put that in the ViewModel.

    1: public void ExportToExcel(Stream fileStream)
    2: {
    3:     var s = Application.GetResourceStream(new Uri("excelTemplate.txt", UriKind.Relative));
    4:     var sr = new StreamReader(s.Stream);
    5:  
    6:     var sw = new StreamWriter(fileStream);
    7:     while (!sr.EndOfStream)
    8:     {
    9:         var line = sr.ReadLine();
   10:         if (line == "***") break;
   11:         sw.WriteLine(line);
   12:     }
   13:  
   14:     foreach (SuperEmployee emp in SuperEmployees)
   15:     {
   16:         sw.WriteLine("<Row>");
   17:         sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Name);
   18:         sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Origin);
   19:         sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Publishers);
   20:         sw.WriteLine("<Cell><Data ss:Type=\"Number\">{0}</Data></Cell>", emp.Issues);
   21:         sw.WriteLine("</Row>");
   22:     }
   23:     while (!sr.EndOfStream)
   24:     {
   25:         sw.WriteLine(sr.ReadLine());
   26:     }
   27: }

Notice it does not interact with the view at all.  The way the View gets the Stream to write the excel data to is totally up to the view.  This makes unit testing easier and is a more clean separation of concerns. 

AddSuperEmployee and the ErrorWindow work in very similar ways. 

 

Unit Testing

No ViewModel post would be complete without at least some mention of unit testing.  One of the key motivators for the ViewModel pattern is the ability to test the UI-logic of your application without having to worry about UI automation.  The most important thing to do when you are unit testing is to focus on testing YOUR CODE.  I happen to know that Microsoft employs lots of great testers and developers to to test our code (in the framework)…  You should focus on isolating just your code and testing that.  So  effectively what we want to do is create another view for our ViewModel (in this case test code) and mock out the networking\data access layer.

First, let’s create a unit test project for our Silverlight client. I am going to use   If you got the Silverlight Unit Test Framework installed correctly, you should see a project template.. (check out Jeff Wilco’s excellent post on getting this installed).

image  

Then you need to add references to Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight.dll and Microsoft.Silverlight.Testing.dll as Jeff says in his post.    You will also need to add a Project Reference to the MyApp project, this contains the code we want to test.

You will see we have our first test in place already.

    1: [TestClass]
    2: public class SuperEmployeesViewModelTest : SilverlightTest
    3: {
    4:     [TestMethod]
    5:     public void TestMethod()
    6:     {
    7:         Assert.IsTrue(true);
    8:     }

To run it, simply set the new MyApp.Tests project as the startup

image

and hit F5.

image

We pass… but that test was clearly not very interesting… let’s look at adding a more interesting test. 

But first, let’s recall the most important part of unit testing – only test the code you wrote.  So for example, I don’t want to test the code that talks to the server, or the code that talks to the database on the server.. all of those are someone else’s code.  So, I want to mock out the connection to the server.   Luckily, DomainContext has a built in way to do this level of mocking.  DomainContext has a DomainService that is responsible for all communication with the server.   We just need to jump in there and provide our own,  MockDomainService that doesn’t hit the server to get data, but rather just uses it’s own locally provided data. 

 

    1: public class MockDomainClient : LocalDomainClient {
    2:  
    3:     private IEnumerable<Entity> _mockEntities;
    4:  
    5:     public MockDomainClient(IEnumerable<Entity> mockEntities) {
    6:         _mockEntities = mockEntities;
    7:     }
    8:  
    9:     protected override IQueryable<Entity> Query(QueryDetails details, 
   10:                                    IDictionary<string, object> parameters) {
   11:         var q = _mockEntities.AsQueryable();
   12:      
   13:         return q;
   14:     }
   15: }

Here is my starter MockDomainClient.. notice I am deriving from the LocalDomainClient (via Nikhil’s excellent ViewModel post) and later we will look at the QueryDetails (via Jason Allor’s LinqService code). 

 

Now, let’s add our first real test… Let’s verify our logic for dealing with the EmployeeIDFilter is correct.  There are three steps to each unit test: (1) setup (2) test (3) verify.

Let’s look at the initialize first. 

    1: [TestMethod]
    2: [Asynchronous]
    3: public void TestLoadData_EmployeeIDFilter()
    4: {
    5:     //initialize 
    6:     var entityList = new List<Entity>()
    7:     {
    8:         new SuperEmployee () {
    9:             Name = "One",
   10:             EmployeeID = 1,
   11:         },
   12:         new SuperEmployee () {
   13:             Name = "Two",
   14:             EmployeeID = 2
   15:         }
   16:     };
   17:  
   18:  
   19:     var client = new MockDomainClient(entityList);
   20:     var context = new SuperEmployeeDomainContext(client);
   21:     var vm = new SuperEmployeesViewModel(context);
   22:  
   23:     vm.ErrorRaising += (s, arg) =>
   24:     {
   25:         throw new Exception("VM throw an exceptions", arg.Exception);
   26:     };
   27:  
   28:  
   29:  
   30:     //run test
   31:     //TODO
   32:  
   33:  
   34:     //check results
   35:     EnqueueDelay(1000);
   36:  
   37:     EnqueueCallback(() =>
   38:     {
   39:        //TODO asserts
   40:     });
   41:     EnqueueTestComplete();
   42: }

 

Notice in line 2, we are making this an async test, because our ViewModel return results back in an async way, we need our test to do the same.  In line 6-16, we are initializing the data.  I really like having all the data right here in the test so it is easy to see what it going on and it is isolated.    In line 19-21, we are creating a MockDomainClient and initializing it with this test data, then we are creating a SuperEmployeeDomainContext based on this mock DomainClient and finally, we create the ViewModel.   In line 23-36, we are handling any errors that may be thrown, useful for debugging test. 

Now, let’s flush out the run and verify steps…

    1: //run test
    2: vm.EmployeeIDFilter = "1";
    3:  
    4:  
    5: //check results
    6: EnqueueDelay(1000);
    7:  
    8:  
    9: EnqueueCallback(() =>
   10: {
   11:     Assert.IsTrue(vm.SuperEmployees.Count() == 1);
   12:     var res = vm.SuperEmployees.FirstOrDefault();
   13:     Assert.IsNotNull(res.EmployeeID == 1);
   14: });
   15: EnqueueTestComplete();
   16: }

To run the tests, we simply set the EmployeeIDFilter to 1.. as a side effect we will load data…   Then in lines 11-13 we do some basic asserts to make sure exactly one item is returned and that it has the right EmployeeID.

Now, we just run it and…we pass! 

image

Testing the OriginFilter looks pretty much the same..

    1: [TestMethod]
    2: [Asynchronous]
    3: public void TestLoadData_EmployeeOriginFilter()
    4: {
    5:     //Setup
    6:     var entityList = new List<Entity>()
    7:     {
    8:         new SuperEmployee () {
    9:             Name = "One",
   10:             EmployeeID = 1,
   11:             Origin = "Earth",
   12:         },
   13:         new SuperEmployee () {
   14:             Name = "Two",
   15:             EmployeeID = 2,
   16:             Origin = "Earth",
   17:         },
   18:         new SuperEmployee () {
   19:             Name = "Three",
   20:             EmployeeID = 3,
   21:             Origin = "Raleigh",
   22:         }
   23:     };
   24:  
   25:  
   26:     var client = new MockDomainClient(entityList);
   27:     var context = new SuperEmployeeDomainContext(client);
   28:     var vm = new SuperEmployeesViewModel(context);
   29:  
   30:  
   31:     //run test
   32:     vm.OriginFilterText = "Earth";
   33:  
   34:  
   35:  
   36:     //check results
   37:     EnqueueDelay(1000);
   38:  
   39:  
   40:     EnqueueCallback(() =>
   41:     {
   42:         Assert.IsTrue(vm.SuperEmployees.Count() == 2);
   43:         foreach (var emp in vm.SuperEmployees)
   44:         {
   45:             Assert.IsTrue(emp.Origin == "Earth");
   46:         }
   47:  
   48:     });
   49:     EnqueueTestComplete();
   50: }

And we run it and pass!

image

I leave it as an exercise to the reader to finish the other tests ;-) 

 

Closing

I hope you enjoyed this overview of ViewModel and RIA Services.  You can  download the full demo files

 

Thanks to Jeff Handley for going above and beyond to help me with this, and to Nikhil, John Papa and Pete Brown for their very useful feedback. 

Update: Vijay Upadya helped me a bit with the unit testing side with LinqUtils…

Comments

  1. authorisation
  2. Invoice-Orders-Order lines i.e. N-level add new/save/cancel
  3. restrict keyboard and clipboard input And on a side note, which probably doesn't belong here but I've started typing.... is there any way to check that a page/usercontrol has been cleared from memory like some equivalent to the old Forms collection?
  • Anonymous
    September 09, 2009
    Hi Brad, these series have been a huge help. I'm wondering what about handling sorting and filtering on the client side? I already have all my entities pulled down via RIA (it's not very many). I'd much rather have the client handle sorting / filtering w/o having to launch another request to the server. I've tried using a CollectionViewSource, setting the Source property to a PagedEntityCollectionView<T> but it throws a "Unsupported type of source for a CollectionView". Any thoughts?

  • Anonymous
    September 09, 2009
    Further to my previous comment just want to confirm the error message is: Unsopported (sic) type of source for a collection view. Complete with typo. That should probably be fixed!

  • Anonymous
    September 10, 2009
    Instead of commanding you use a thin code-behind layer calling the vm methods. Is this your recommendation or will it be replaced by commands in a future blog entry?

  • Anonymous
    September 10, 2009
    > Instead of commanding you use a thin code-behind > layer calling the vm methods. Is this your >recommendation or will it be replaced by >commands in a future blog entry? Rolf - Yea, i thought about commanding but decided to start simple... The blog post is already very long (too long?) and i don't want to add another concept.  Nikhil has a great example of commanding on his blog..  

  • Anonymous
    October 01, 2009
    Is it possible to host a .Net Ria Service in a WinForm/WPF/Consol or  Application or service too? If yes a a Blog (Part 26) would be nice.

  • Anonymous
    October 12, 2009
    I want to have more than one control on the page showing the data. For example, two SuperEnployee lists, each have its filtering, sorting, ordering and paging. But currently action made in one list will affect the view on both lists. Because they are bound to the entity table itself. Anyway we can get around that? The idea is I want to view up-to-date data ie, if value is changed in one control, all the controls is sync because they all bound to the same EF at the back.

  • Anonymous
    October 15, 2009
    Hi Brad, I'd like to thank you for this great examples because I've learned a lot from it. I think this series should be posted in the oficial RIA Services site, because the samples that are available there aren't this good. In this sample you have a few collection implementations designed to be Bound to any control who listen to ICollectionView and other interfaces. That is great because you can have all your logic in the viewmodel and bind your collections to any Selector control or even datagrids and it should work. I'd like to know if you coded those classes or if you've got it from somewhere else, because the implementation feels incomplete (GroupDescriptors return null, etc), and it would be great to have a full viewmodel ready ICollectionView/IEditableCollectionView implementation. Maybe we could expect that to be released in a future version of RIA Services? Since the implementation you provided in your sample works only with RIA entities. Best regards, MF.

  • Anonymous
    October 21, 2009
    Select any item in the grid. In the DataForm, clear the name. Now, select another item in the grid. Boom. How can this be handled? /ingo

  • Anonymous
    October 28, 2009
    Hi Brad , how can i bind and get selected value in MVVM model ,because in combobox we are not able to get selected value. and how can i provide relative binding in silver light?

  • Anonymous
    November 10, 2009
    Hi, Unfortunately the file with full demo files is unavailable: http://brad_abrams.members.winisp.net/Projects/Silverlight3RTM/MyApp.ViewModel.zip">http://brad_abrams.members.winisp.net/Projects/Silverlight3RTM/MyApp.ViewModel.zip It looks like the site is down: http://brad_abrams.members.winisp.net/ Could you please upload the file to another site?

  • Anonymous
    November 19, 2009
    I downloaded the files and tried to run the app but they don't work because seo has been eliminated. Do you have an updated set that will run with the latest Silverlight release?

  • Anonymous
    November 19, 2009
    Sorry about the Alan, I have not updated this for the latest RIA Services bits...

  • Anonymous
    November 25, 2009
    Hello Brad, In this example you implemented an EntityCollectionView class and a PagedEntityCollectionView. Is there any reason why you didn't use the System.Windows.Data.PagedCollectionView class? Thanks, MF.

  • Anonymous
    November 26, 2009
    I think the only change required to get this to run is to change EntityList to EntitySet. It looks like EntityList was removed in the Beta.