Table of Contents
I started using Silverlight a few weeks ago, and it looks really amazing. Combining the presentation powers of WPF with the strong capabilities of C#, Silverlight looks very powerful. The main focus of this article will be to retrieve data from a database and consume it in a Silverlight application. If you are an ASP.NET developer and want to start exploring Silverlight, then perhaps this is the right place to start since we will also be looking at similarities between Silverlight and ASP.NET.
We will be developing our first business application in Silverlight. Since almost every business application has something to do with data and databases, we will be looking at how data (from a database) can be displayed inside a Silverlight application. Taking the Northwind database as an example, we will be generating some LINQ classes, a WCF service for retrieving data, and finally, a ListBox
and a DataGrid
along with DataTemplate
s for data presentation. Our output will be a fairly simple master-detail UI with customers, their orders, and order details. Note that the source code of this article is written targeting Silverlight 2 RC1.
A small tweak is needed to run the sample project. By default, there will be no startup project for the solution, so we must manually set DataApplication.Web
as the startup project by bringing up the properties window of the solution.
If you are an ASP.NET programmer, it may be important for you to note that the C# code in the Silverlight project runs on the "client" and not on the server. Perhaps, you can think of it as JavaScript. Thus, the good thing with Silverlight is that you now have full control on the client side without knowing or writing any JavaScript (at least, I am not good at writing JavaScript).
Well, the simple reason is that since the C# code runs on the client, our server databases will not be accessible directly. Note that we do not have items like DataSet
or DataSource
in a Silverlight project. The namespace System.Data
that contains our favorite classes is also absent. But, the good thing is that there are other alternatives too, e.g., a WCF Service. This article is going to demonstrate how to use a WCF service for data retrieval.
To start creating Silverlight applications using Visual Studio, we need to install Silverlight tools for Visual Studio that can be downloaded from here. After installing the add-on, two new project types, Silverlight Application and Silverlight Class Library, will be added. We will start by creating a new "Silverlight Application" project named DataApplication.
Visual Studio will then ask how we want to deploy our Silverlight application; we are going to select "ASP.NET Web Application Project" in which we will create the LINQ classes and the WCF service.
If all things go correctly, we will be able to see two projects in our solution: DataApplication
will be our client-side Silverlight project, while DataApplication.Web
will be our ASP.NET server-side project.
Our lightweight all-rounder Northwind (available from this link) is always one of the best databases to start with. Note that the MDF is also included in the sample project, and is connected via SQLExpress. To start generating LINQ data classes, we need to add a new LINQ Data Classes object in our ASP.NET application.
Now, using the Server Explorer, create a new data connection to the Northwind database (either using SQLExpress, or SQL Server if you have it), and then drag the tables Customer
, Orders
, and OrderDetails
into the LINQ designer. One important thing is to allow serialization of our LINQ generated data classes since we will be transferring these objects to our Silverlight application. To achieve this, click on some empty space in the LINQ designer and change the property Serialization
to UniDirectional
using the Properties window.
Now, we are about to add a service to our server project for retrieving data. Before Silverlight 2 Beta 2, we needed some tweaks with the standard WCF service template to use in a Silverlight project, but fortunately, Beta 2 and newer versions give us the "Silverlight-Enabled WCF service" template that handles all things itself. We are going to add a new Silverlight-enabled WCF Service named DataService to our ASP.NET project.
We will write three methods in our service, one that returns all the customers, one that returns orders of a customer, and finally, one that returns order details of a particular order. Note that the methods must be marked with the attribute [OperationContract]
(this attribute is similar to a [WebMethod]
attribute for an ASMX service). Here is a quick implementation of the data retrieval methods using very basic LINQ. Add the following to the DataService.svc.cs file:
[OperationContract]
public List<Customer> GetCustomers()
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return datacontext.Customers.ToList();
}
[OperationContract]
public List<Order> GetOrders(string customerID)
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return (from order in datacontext.Orders
where order.CustomerID == customerID
select order).ToList();
}
[OperationContract]
public List<Order_Detail> GetOrderDetails(int orderID)
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return (from orderdetail in datacontext.Order_Details
where orderdetail.OrderID == orderID
select orderdetail).ToList();
}
That's all that was required on the server-side ASP.NET project. We have created LINQ data classes to get data from the database and a WCF service to return those LINQ objects. Now, we are ready to consume the service in our client-side Silverlight project. For this, we need to add a service reference in the DataApplication
project. We can click Discover in the Add Service Reference popup to have Visual Studio automatically find the newly created WCF service for us.
A Silverlight page/control consists of a layout XAML file and a code-behind xaml.cs file. Typically, similar to an ASP.NET page, the XAML file contains the layout definition (like a *.aspx file where we define our UI), while the xaml.cs file contains the logic and event handlers (like our aspx.cs file). Let us start by creating some basic layout for our application.
We will display our data using the DataGrid
control, but Silverlight does not include a reference to the DataGrid
control by default, so we need to add one. This process is very similar to using a custom control in ASP.NET. Recall that we add a reference to the appropriate DLL in the project and then add a register
tag in the aspx page. To achieve this in Silverlight, add a new reference (right click on References and select New Reference) and locate System.Windows.Controls.Data
in the list (this is the assembly that contains the DataGrid
).
After this reference is added, we need to assign a namespace to this assembly in our XAML markup. To do this, add the following to the namespace declaration of the file Page.Xaml.
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
The Visual Studio IDE assists us here as the following screenshot shows:
Here's an outline of what to do: Create a Grid
named LayoutRoot
with three rows: the first one for our Application Title (width=50), the third one for a status bar (width=20), and the middle row acting as the main content holder (width=*, takes all the space that is left). Add a title TextBlock
to the first row, and an empty TextBlock
named txtStatus
to the bottom row of the LayoutRoot
grid. In the middle row (the one we identified as the content holder) of the LayoutRoot
grid, add another Grid
named ContentRoot
with two columns and two rows; the left column with width 200 and the right column taking the rest. The rows should be divided in the ratio 60% and 40%. In the left column of this ContentRoot
grid, add a ListBox
that spans in both rows. In the right column, add a DataGrid
for the customer orders in the first row and another DataGrid
for the order details in the second row. Tired up with my crazy sentences, here's the XAMLified version for page.xaml:
<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls"
x:Class="DataApplication.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
Width="Auto" Height="Auto">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="55" x:Name="HeaderRow" />
<RowDefinition Height="*" x:Name="ContentRow"/>
<RowDefinition Height="20" x:Name="FooterRow"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
-->
<TextBlock x:Name="txtHeader" Grid.Row="0"
FontSize="20" Margin="5,5" Foreground="Blue"
Text="My First Data Application in Silverlight">
</TextBlock>
-->
<TextBlock x:Name="txtStatus" Grid.Row="2"
FontSize="10" Margin="5,0" Foreground="Red">
</TextBlock>
-->
<Grid x:Name="ContentGrid" Grid.Row="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height=".6*" />
<RowDefinition Height=".4*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
-->
<ListBox x:Name="lstCustomers" Grid.Column="0" Grid.RowSpan="2"
DisplayMemberPath="ContactName"
Loaded="lstCustomers_Loaded"
SelectionChanged="lstCustomers_SelectionChanged">
</ListBox>
-->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"
AutoGenerateColumns="True"
SelectionChanged="dgOrders_SelectionChanged">
</data:DataGrid>
-->
<data:DataGrid x:Name="dgOrderDetails" Grid.Row="1" Grid.Column="1"
AutoGenerateColumns="True"
AutoGeneratingColumn="dgOrderDetails_AutoGeneratingColumn">
</data:DataGrid>
</Grid>
</Grid>
</UserControl>
I will not be explaining the WPF layout in much detail in this article as there are a lot of resources on The Code Project as well as outside; e.g., this one from Sacha Barber. We will rather have a quick look on the markup for the ListBox
and the DataGrid
.
The listbox lstCustomers
will be used for displaying the list of customers in the database. We will be binding this listbox inside the Loaded
event for which we have registered. Note that if a ListBox
is bound to some object source, it will display the value object.ToString()
for each of its items collection. If we want to display any other value (typically a string
member of the object), we have three apparent choices:
- Override the
object.ToString()
method (We are not going to do this just for displaying the correct values in a ListBox
).
- Define some Data Template (this is the most flexible approach, and we will briefly look at
DataTemplate
s in this article when we will manually define the columns for our DataGrid
, but for now, we will skip this choice).
- Define the property to be used in the
DisplayMemberPath
property of the ListBox
(fairly simple, so we will keep ourselves to this for now).
Since we want the ListBox
to show the ContactName
property of the Customer
object to which it is bound, we use DispalyMemberPath="ContactName"
. Also, we have registered for the SelectionChanged
event which we will handle in our code-behind file to update the DataGrid
with the selected customer's orders.
Currently, we are not doing anything fancy with DataGrid
s. We have just configured them to auto-generate their columns when they are data bound. Note that we also subscribed for the AutoGeneratingColumns
event in dgOrderDetails
. This is a common practice, and used in conjunction with auto-generating columns if we want to remove certain unwanted columns quickly. In this article, we will also be looking at how to define columns manually, but for now, let us keep it simple.
We want to load all the customers into the listbox lstCustomers
, for which we are using the Loaded
event of the ListBox
. Note that all service calls in Silverlight need to be asynchronous, so we will register a callback function where we will bind the incoming data to the ListBox
. Notice how we use the txtStatus
textbox (recall we placed this in the bottom row of LayoutGrid
to provide updates to the user).
private void lstCustomers_Loaded(object sender, RoutedEventArgs e)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading customers...";
svc.GetCustomersCompleted += new
EventHandler<GetCustomersCompletedEventArgs>(svc_GetCustomersCompleted);
svc.GetCustomersAsync();
}
void svc_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
if (e.Error == null)
{
this.lstCustomers.ItemsSource = e.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading customers from database";
}
}
Now, we will write some code to display the orders when a customer in the listbox is selected. In the SelectionChanged
event handler, we will call our WCF service and bind the data to dgOrders
when it is retrieved. This time, we will use anonymous methods to be more compact.
private void lstCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Customer selectedCustomer = this.lstCustomers.SelectedItem as Customer;
if (selectedCustomer != null)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading orders...";
svc.GetOrdersCompleted +=
delegate(object eventSender, GetOrdersCompletedEventArgs eventArgs)
{
if (eventArgs.Error == null)
{
this.dgOrders.ItemsSource = eventArgs.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading orders from database";
}
};
svc.GetOrdersAsync(selectedCustomer.CustomerID);
}
}
Similar to the listbox SelectionChanged
event, we add the following code to the SelectionChanged
event of dgOrders
. This time, we will try lambda expressions.
private void dgOrders_SelectionChanged(object sender, EventArgs e)
{
Order selectedOrder = this.dgOrders.SelectedItem as Order;
if (selectedOrder != null)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading order details...";
svc.GetOrderDetailsCompleted +=
(eventSender, eventArgs) =>
{
if (eventArgs.Error == null)
{
this.dgOrderDetails.ItemsSource = eventArgs.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading order details from database";
}
};
svc.GetOrderDetailsAsync(selectedOrder.OrderID);
}
}
Note that in XAML, we set the AutoGenerateColumns
property of the DataGrid
s to true
. Let us say we want to get rid of the OrderID
column in the dgOrderDetails
datagrid. This is achieved by writing the following code in the AutoGeneratingColumns
event handler:
private void dgOrderDetails_AutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
if (e.Column.Header.ToString() == "OrderID")
e.Column.Visibility = Visibility.Collapsed;
}
Now, our small application is ready to be viewed. Run it.. Select some customers, view orders, edit the data displayed in the DataGrid
, sort the DataGrid
data by clicking on the column headers, resize the DataGrid
columns, resize the browser window, do whatever else you want. Wasn't that cool? We will now have a look at the DataGrid
columns and templates.
Columns in a Silverlight DataGrid
can be defined in a way very similar to what we do in an ASP.NET. The DataGrid
can accept three types of columns:
DataGridTextBoxColumn
- This column type uses a TextBlock
to display its data, and a TextBox
to allow editing of its data. We need to tell which property of the data-bound object to display, using DisplayMemberPath
.
DataGridCheckBoxColumn
- This column type provides a read-only CheckBox
for displaying a boolean or nullable boolean value, and a normal CheckBox
to allow editing of that value.
DataGridTemplateColumn
- This powerful column type allows us to define DataTemplate
s and select controls of our own choice just like ASP.NET's TemplateColumn
. More on DataTemplating from MSDN here.
If you want to get more, Scott Morris has a nice blog entry on column types here.
So, let's use this knowledge in our application. For the sake of simplicity, we will define only four columns: we will use a DataGridTextBoxColumn
for columns OrderID
and EmployeeID
. We will define a DataGridTemplateColumn
for OrderDate
with a TextBlock
in its CellTemplate
and a DatePicker
control in its CellEditingTemplate
. Finally, we will define another TemplateColumn
for Frieght
. But, this time, we will define two controls in its CellEditingTemplate
: a Slider
for increasing/decreasing the Frieght
value, and a TextBlock
to show the current value of the slider. Both these controls will be placed in a horizontal StackPanel
as we can only define a single item in a DataTemplate
.
Here's the code that should replace the dgOrders
markup:
<!---->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"
AutoGenerateColumns="False"
SelectionChanged="dgOrders_SelectionChanged">
<data:DataGrid.Columns>
<!---->
<data:DataGridTextColumn Header="Order ID" Binding="{Binding OrderID}" />
<!---->
<data:DataGridTextColumn Header="Employee ID" Binding="{Binding EmployeeID}" />
<!---->
<data:DataGridTemplateColumn Header="Order Date" Width="150">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OrderDate}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<basics:DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
<!---->
<data:DataGridTemplateColumn Header="Freight" Width="150">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Freight}"></TextBlock>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Freight}" Width="50" />
<Slider Value="{Binding Freight, Mode=TwoWay}" Width="100"
Minimum="0" Maximum="500" />
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
In a similar way, we can use DataTemplate
s for our ListBox
. Suppose in our object model, we have a PictureProperty
(returning a BitmapImage
) of a Customer
. Then, we could have displayed the picture in the ListBox
like this:
<ListBox x:Name="lstCustomer">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding NameProperty}"></TextBlock>
<Image Source="{Binding PictureProperty}"></Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that the above code is just a sample. Since we do not have pictures in our objects, we cannot use this snippet in our application.
Run the project again. Double click on an OrderDate
and observe how the DatePicker
pops up. Use the slider control to modify the Freight
value. Similarly, any other control or combination of controls can be used inside an item template. Note that in this second iteration, we did not modify the *.cs file; all the changes we did were on the layout side. This is the beauty of Windows Presentation Foundation framework.
Well, this will be too much for this introductory article. But, if you have followed how the WCF service is used to communicate between the client and the server, you can easily write data back to the database by creating some functions in the service and calling them from the Silverlight application. Notice that the data bindings in this article are two way, that is, changing the value inside a DataGrid
actually changes the value in the bound DataContext
item. We just need to send these updates to our ASP.NET project using the WCF service and handle it there to update the database. Ronnie Saurenmann presents a couple of videos here. He uses a DataSet
like approach, keeping the modified and the original copies of data, and then sending only the relevant records to the server using some helper classes. I strongly recommend watching his videos.
That's all. I wrote this article to demonstrate how easy it is to build the foundation of any data application. Let us revise what we learnt: we created a data access layer using LINQ, and exposed it using a WCF service in our service side ASP.NET project. We retrieved data using the service client in our Silverlight application, and finally, used some data templates to have more control over data presentation. I hope this article created some motivation to start building your future applications in Silverlight. Happy Silverlighting...
- 12 Aug 08 - Article posted
- 19 Oct 08 - Article updated for Silverlight 2 RC1