April 2009 - Posts
Using the declarative DomainDataSource that is part of the upcoming Silverlight 3 RIA services makes it quite easy to work with data. All you need to do is add a DomainDataSource control to the the XAML, point it to the generated DomainContext class (in this case NorthwindContext) and tell it which method to use to load the data from the web service(in this case LoadCustomers). Next add a DataGrid to display the data and you are good to go.
<UserControl xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm" x:Class="LOBUsingRIAServices.CustomerListPage"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
xmlns:web="clr-namespace:LOBUsingRIAServices.Web"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<riaControls:DomainDataSource x:Name="CustomerDataSource"
LoadMethodName="LoadCustomers">
<riaControls:DomainDataSource.DomainContext>
<web:NorthwindContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<data:DataGrid ItemsSource="{Binding Data, ElementName=CustomerDataSource}" />
</Grid>
</UserControl>
Pretty simple and that is the way I like it 
Adding paging.
Sometimes the lost of data to load can get somewhat large and you might not want to load all data. In that case all you need to do is add a PageSize and the DomainDataSource will only load enough data to display on a single page. You can do this by just setting the PageSize on the DomainDataSource but as we also need a control to allow the user to page trough the data it is easier to also add the DataPager control. Now you have the option of setting the PageSize on the DomainDataSource or the DataPager. I found that setting it on either would work just was well except for the initial load where the DataPager shows page 0 when the PageSize is set on the DomainDataSource while it is set to 1, the correct value, when set on the DataPager. I assume this is just a small bug in the current preview.
Another thing you can specify is the LoadSize. This determines how many rows are loaded with each request and if not set equals the PageSize. Setting this to double the PageSize will improve the responsiveness of the the client application so might be a good idea if the data isn’t too large.
<UserControl xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm" x:Class="LOBUsingRIAServices.CustomerListPage"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
xmlns:web="clr-namespace:LOBUsingRIAServices.Web"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<riaControls:DomainDataSource x:Name="CustomerDataSource"
LoadMethodName="LoadCustomers"
LoadSize="40">
<riaControls:DomainDataSource.DomainContext>
<web:NorthwindContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<data:DataGrid ItemsSource="{Binding Data, ElementName=CustomerDataSource}" />
<dataControls:DataPager Source="{Binding Data, ElementName=CustomerDataSource}"
PageSize="20" />
</Grid>
</UserControl>
The DataPager control is quite easy to use as well. Just drop it below the DataGrid and point it to the DomainDataSource to use and it just works. One problem I ran into was adding the DataPager above the DataGrid though. When I did that the DataPager didn’t show up on the UI so the user could not page through the data even though it still limited the data to the first page.
Using progressive loading
Another nice option is delayed loading. When using progressive loading all you need to do is specify the LoadSize on the DomainDataSource. The DomainDataSource will now load the specified number of rows, wait a bit and load the next set of rows. It will keep on doing this until all data is loaded. The nice thing here is that all the data is loaded but the UI still stays responsive for the user. De the default interval between load requests is 0.75 seconds but this can be fine tuned using the LoadDelay time span on the DomainDataSource.
<UserControl xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm" x:Class="LOBUsingRIAServices.CustomerListPage"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
xmlns:web="clr-namespace:LOBUsingRIAServices.Web"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<riaControls:DomainDataSource x:Name="CustomerDataSource"
LoadMethodName="LoadCustomers"
LoadSize="10"
LoadDelay="0:0:0.25">
<riaControls:DomainDataSource.DomainContext>
<web:NorthwindContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<data:DataGrid ItemsSource="{Binding Data, ElementName=CustomerDataSource}" />
</Grid>
</UserControl>
And all of this was done without writing any code add everything was completely declarative. Of course it is just a matter of setting properties so doing so from code is easy enough.
Enjoy!
Normal
0
21
false
false
false
MicrosoftInternetExplorer4
<!--
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-parent:"";
margin:0cm;
margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:12.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";}
a:link, span.MsoHyperlink
{color:blue;
text-decoration:underline;
text-underline:single;}
a:visited, span.MsoHyperlinkFollowed
{color:purple;
text-decoration:underline;
text-underline:single;}
@page Section1
{size:612.0pt 792.0pt;
margin:70.85pt 70.85pt 70.85pt 70.85pt;
mso-header-margin:35.4pt;
mso-footer-margin:35.4pt;
mso-paper-source:0;}
div.Section1
{page:Section1;}
-->
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}
Normally you should never have to set the <machineKey> in the in the web.config. This is only needed when am ASP.NET site runs on a web farm and in that case the machines in question should be configured using the machineKey instead of individual web applications. However we have run into the following exception a few times: “System.Web.UI.ViewStateException: Invalid viewstate” after leaving the browser idle for some time.
Turns out that the <machineKey> that where supposed to be on the web farm where missing. Now this is a shared hoster so not much we can do about the machine configuration but adding this key to the web.config before deploying is simple enough.
And generating the values to add is simple using this nifty, not my words, little generator.
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
The View – Model – ViewModel design pattern, also known as MVVM, is getting more popular these days. I have found it extremely easy to use when developing very different applications and have used the design pattern recently in both ASP.NET, WPF and Silverlight applications. However easy as it might be is seems to confuse people as I have seen some terrible examples where people make a complete mess of things.
Josh Smith did an excellent screen cast for Pixel8 on using MVVM with WPF, you can find it here.
Even thought the UI technology used doesn't change the basic MVVM pattern there are some subtle differences, like not easily being able to use ICommand in Silverlight, so I decided to create a small Silverlight sample.
The basic structure goes like this:
- The user interacts with a View, implemented as a Silverlight user control.
- The View is data bound to a ViewModel. This is the most important step to remember. The ViewModel is just another class.
- The ViewModel is a wrapper for a Model. Think of the Model as the data and the business rules.
The Model
The Model I am using is very simple and has two read-write properties, FirstName and LastName, and a single read-only property, FullName. The FullName property represents some business rule on how a name should be formatted. I know it is a bit of a lame business rule but it is just a sample
. The Model also implements INotifyPropertyChanged so the UI can be updated whenever a value is updated. The complete model looks like this:
1: using System.ComponentModel;
2:
3: namespace SilverlightMVVMDemo.Model
4: {
5: public class Customer : INotifyPropertyChanged
6: {
7: private string _firstName;
8: private string _lastName;
9:
10: public string FirstName
11: {
12: get { return _firstName; }
13: set
14: {
15: _firstName = value;
16: OnPropertyChanged("FirstName");
17: OnPropertyChanged("FullName");
18: }
19: }
20: public string LastName
21: {
22: get { return _lastName; }
23: set
24: {
25: _lastName = value;
26: OnPropertyChanged("LastName");
27: OnPropertyChanged("FullName");
28: }
29: }
30:
31:
32: public string FullName
33: {
34: get { return FirstName + " " + LastName; }
35: }
36:
37: public event PropertyChangedEventHandler PropertyChanged;
38:
39: protected void OnPropertyChanged(string propertyName)
40: {
41: if (PropertyChanged != null)
42: {
43: var args = new PropertyChangedEventArgs(propertyName);
44: PropertyChanged(this, args);
45: }
46: }
47: }
48: }
The View
The View is just a Silverlight user control that makes things visible for the user. All controls use data binding to get to their values. There is one interesting twist here. Just like I had a business rule about how to format a FullName I have a rule about the font weight to use to display the FullName. This is just UI so not a business rule and as a result it is not stored in the Model. We do not want to code this in the UI as that makes things hard to test. So that leaves one place, the ViewModel
. And the View uses data binding to set the FontWeight property.
The complete View looks like this:
1: <UserControl
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:SilverlightMVVMDemo_ViewModel="clr-namespace:SilverlightMVVMDemo.ViewModel" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Class="SilverlightMVVMDemo.View.CustomerView"
5: Width="400" Height="300" mc:Ignorable="d">
6: <Grid x:Name="LayoutRoot"
7: Background="White">
8: <Grid.RowDefinitions>
9: <RowDefinition Height="0.133*"/>
10: <RowDefinition Height="0.133*"/>
11: <RowDefinition Height="0.133*"/>
12: <RowDefinition Height="0.18*"/>
13: <RowDefinition Height="0.42*"/>
14: </Grid.RowDefinitions>
15: <Grid.ColumnDefinitions>
16: <ColumnDefinition Width="0.3*"/>
17: <ColumnDefinition Width="0.7*"/>
18: </Grid.ColumnDefinitions>
19: <TextBlock Text="Firstname:"
20: Margin="8"/>
21: <TextBox Text="{Binding Path=FirstName, Mode=TwoWay}"
22: Grid.Column="1"
23: Margin="8,8,8,8"/>
24: <TextBlock Text="Lastname:"
25: Grid.Row="1"
26: Margin="8"/>
27: <TextBox Text="{Binding Path=LastName, Mode=TwoWay}"
28: Grid.Row="1"
29: Grid.Column="1"
30: Margin="8,8,8,8"/>
31: <TextBlock Text="Fullname:"
32: Grid.Row="2"
33: Margin="8"/>
34: <TextBox Text="{Binding Path=FullName}"
35: Grid.Row="2"
36: Grid.Column="1"
37: Margin="8,8,8,8"
38: FontWeight="{Binding Path=FullNameFontWeight}"
39: IsReadOnly="True"/>
40: <Button Content="Save"
41: Click="Button_Click"
42: Grid.Row="3"
43: Grid.Column="1" />
44: </Grid>
45: </UserControl>
The code behind is very simple and looks like this:
1: using System.Windows.Controls;
2: using SilverlightMVVMDemo.ViewModel;
3:
4: namespace SilverlightMVVMDemo.View
5: {
6: public partial class CustomerView : UserControl
7: {
8: public CustomerView()
9: {
10: InitializeComponent();
11: DataContext = new CustomerViewModel();
12: }
13:
14: private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
15: {
16: var viewModel = (CustomerViewModel)DataContext;
17: viewModel.Save();
18: }
19: }
20: }
In an ideal world there would be no code at all, or only that setting the DataContext but as Silverlight makes it a little harder to use commands there is a single line, okay two counting the cast, to pass the save command on to the ViewModel.
The ViewModel
The ViewModel class is the one that glues the View and the Model together, hence its name
. basically the ViewModel wraps each Model object, so if you want to use a collection of customer models you create a collection of CustomerViewModel objects and databind against them. The ViewModel catches all PropertyChanged events from the Model and passes them on to the user interface. Whenever the FullName property changes the FullNameFontWeight might also change so in that case an extra PropertyChanged event is raised. Note that calculation of the FontWeight to use is part of the ViewModel as the UI depends on this but it is not a business rule per se.
Another example of a property like this would be the CSS class to use in an ASP.NET application. basically if a UI property changes, create a property get to do so and databind it.
The complete ViewModel class looks like this:
1: using System.ComponentModel;
2: using System.Windows;
3: using SilverlightMVVMDemo.Model;
4:
5: namespace SilverlightMVVMDemo.ViewModel
6: {
7: public class CustomerViewModel : INotifyPropertyChanged
8: {
9: private Customer _model;
10:
11: public CustomerViewModel()
12: {
13: Model = new Customer() { FirstName = "Maurice", LastName = "de Beijer" };
14: }
15:
16: public CustomerViewModel(Customer model)
17: {
18: Model = model;
19: }
20:
21:
22: public Customer Model
23: {
24: get { return _model; }
25: private set
26: {
27: if (_model != null)
28: _model.PropertyChanged -= ModelPropertyChanged;
29:
30: _model = value;
31:
32: if (_model != null)
33: _model.PropertyChanged += ModelPropertyChanged;
34: }
35: }
36:
37: void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
38: {
39: OnPropertyChanged(e.PropertyName);
40:
41: if (e.PropertyName == "FullName")
42: OnPropertyChanged("FullNameFontWeight");
43: }
44:
45:
46: public string FirstName
47: {
48: get { return Model.FirstName; }
49: set { Model.FirstName = value; }
50: }
51: public string LastName
52: {
53: get { return Model.LastName; }
54: set { Model.LastName = value; }
55: }
56:
57: public string FullName
58: {
59: get { return Model.FullName; }
60: }
61:
62: public FontWeight FullNameFontWeight
63: {
64: get
65: {
66: if (FullName.Length > 20)
67: return FontWeights.ExtraBold;
68: else if (FullName.Length > 15)
69: return FontWeights.Bold;
70: else if (FullName.Length > 10)
71: return FontWeights.Normal;
72: else
73: return FontWeights.Light;
74: }
75: }
76:
77: public event PropertyChangedEventHandler PropertyChanged;
78:
79: protected void OnPropertyChanged(string propertyName)
80: {
81: if (PropertyChanged != null)
82: {
83: var args = new PropertyChangedEventArgs(propertyName);
84: PropertyChanged(this, args);
85: }
86: }
87:
88: internal void Save()
89: {
90: // Implement the logic to save the customer.
91: }
92: }
93: }
In this case I duplicated all properties from the Model in the ViewModel. This is not a must, some people prefer to expose and bind to the Model directly. It saves a few lines of code but I prefer not to do so. Of course there is no need to duplicate properties the UI is never going to bind to directly.
Model-View-ViewModel guidelines
Not very difficult at all as long as you keep to a few guidelines:
- Never set any UI properties from your CS code with the exception of the DataContext.
- If you need an event handler in your code behind, like the button click, make it a single line and pass the request onto the ViewModel.
- All UI control properties that need to change at runtime do so by data binding to a ViewModel property.
- Organize your ViewModel types per View and not per Model. It is perfectly fine to create a ViewModel that wraps two different Models. A View has only a single DataContext so you cannot bind it to multiple ViewModel objects.
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
And now on Twitter at http://twitter.com/mauricedb.
I was working on an article about the .NET RIA Services and decided to fire up Fiddler to take a close look at the data actually passed around. I was sort of expecting to see a WCF REST based approach with XML messages. Turns out I was wrong, though I expect this option will be added in the future.
It turns out the .NET RIA Services use JSON as their serialization format. This is actually kind of nice as the DataContractJsonSerializer is part of the System.ServiceModel.Web assembly and therefore usable by all .NET clients.
The good part is that a .NET RIA Service is also usable from a JavaScript browser based client. So the same .NET RIA Services library gives us lots of client options. Now this was always the plan but it is nice to see that the default already enable it.
And the best thing?
Well that is that I was already using the .NET RIA Services and never even needed to know how the messages where actually send across the wire, something I shouldn't have to worry about when developing a RIA style application. Which is exactly the goal of the .NET RIA Services.
Cool stuff and highly recommended if you want to create browser based LOB apps 
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
And now on Twitter at http://twitter.com/mauricedb.
As was to be expected the Surface machine at the last SDE was a big success and with lots of people wanting to have a go at it.
Nice to see the interest. And I am grateful Martin Tirion from the Dutch Microsoft office let us borrow the machine for a day.
At the end of June we will have the next big event 
Cu there.
BTW you can find more pictures from the event here.
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
And now on Twitter at http://twitter.com/mauricedb.