Although this chapter primarily focuses on consuming data with RIA Services, it's worth briefly looking first at how you can consume data using plain old WCF Services.
In Chapter 4, we created a WCF Service that returned a collection of Product
entities from an Entity Framework model. You can consume data from a WCF Service in Silverlight in essentially the same way as you would in a Windows Forms or WPF application; you're just forced into using the asynchronous methods when using Silverlight, as the synchronous methods are not available.
Note For each operation in your WCF Service (for example, GetProducts
), the service reference proxy generator will generate a corresponding method (the operation's name, suffixed with Async
; for example, GetProductsAsync
) on the service reference proxy class that will begin the call to the server. A corresponding event will also be created (with the operation's name, suffixed with Completed
; for example, GetProductsCompleted
), that will be raised when the call to the server completes.
There are a number of ways you can connect to a WCF service. You can use the ChannelFactory
class, or you can create a class that inherits from the generic System.ServiceModel.ClientBase
class. However, the easiest way is to use a service reference proxy, which is the method we'll use in this workshop. We'll call the GetProducts
method on the WCF Service we created in Chapter 4, and display the results in a DataGrid
control.
ProductService.svc
service you created in Chapter 4 should appear in the list, as shown in Figure 5-1.Figure 5-1. Visual Studio's Add Service Reference window
Note You might recall from Chapter 4 that Silverlight does not support all the WCF bindings available with the full .NET Framework. If you attempt to add a reference to a service that has no endpoint using a binding supported by Silverlight, the service reference will still be added to your project, but a warning will appear in the Error List window stating that the endpoint is not compatible with Silverlight. Your project will compile, but attempting to make a call to the service will result in an InvalidOperationException
exception.
You'll note that a file named ServiceReferences.ClientConfig
has been added to your project. As its name suggests, this file contains the configuration of the service references in your project.
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="CustomBinding_ProductService">
<binaryMessageEncoding />
<httpTransport maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://localhost:52878/ProductService.svc"
binding="customBinding"
bindingConfiguration="CustomBinding_ProductService"
contract="Services.ProductService"
name="CustomBinding_ProductService" />
</client>
</system.serviceModel>
</configuration>
As you can see, the configuration defines an endpoint that the service reference proxy will use to connect to the WCF Service on the server. Endpoints consist of an
The address points to the WCF Service's URL (when running locally using the ASP.NET Development Server). The endpoint's binding points to a custom binding that is also configured in the file (the custom binding uses binary message encoding for smaller packet sizes). The final piece, the contract, points to the class that the Add Service Reference window generated for us.
System.Windows.Controls.Data.dll
assembly. This assembly contains the DataGrid
control. An alternative way to reference this assembly is to simply drag the DataGrid
control from the toolbox onto a view's design surface.WCFServiceTestView.xaml
, using the Silverlight Page item template. Add a DataGrid
control to this view, and name it productDataGrid
. You should have the following XAML:
<navigation:Page x:Class="AdventureWorks.Views.WCFServiceTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;??
assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="WCFServiceTestView Page">
<Grid x:Name="LayoutRoot">
<sdk:DataGrid Name="productDataGrid" />
</Grid>
</navigation:Page>
WCFServiceTestView.xaml.cs
). Add the following using
statement to the top of the file:
using AdventureWorks.Services;
GetProductsCompleted
event, and call its GetProductsAsync
method, as follows:
public WCFServiceTestView()
{
InitializeComponent();
ProductServiceClient service = new ProductServiceClient();
service.GetProductsCompleted += service_GetProductsCompleted;
service.GetProductsAsync();
}
In the completed event handler, you can then check if an error occurred; if not, you can assign the result to the DataGrid
control's ItemSource
property (which will display the results in the DataGrid
):
private void service_GetProductsCompleted(object sender,
GetProductsCompletedEventArgs e)
{
if (e.Error == null)
{
productDataGrid.ItemsSource = e.Result;
}
else
{
MessageBox.Show(e.Error.Message);
}
}
Alternatively, instead of wiring up the event handler for the GetProductsCompleted
event in the constructor, you can “inline” the call completed logic using an anonymous method, like so:
public WCFServiceTestView()
{
InitializeComponent();
ProductServiceClient service = new ProductServiceClient();
service.GetProductsCompleted += (sender, e) =>
{
if (e.Error == null && !e.Cancelled)
{
productDataGrid.ItemsSource = e.Result;
}
else
{
MessageBox.Show(e.Error.Message);
}
};
service.GetProductsAsync();
}
#/WCFServiceTestView
as the “bookmark” portion of the URL in your browser's address bar.) The DataGrid
control will be populated with the data returned by the WCF Service. Note You can configure the URL for the WCF Service in the ServiceReferences.ClientConfig
file, but sometimes you need to specify the correct URL at runtime. The constructor for the service reference proxy class has several overloads, some of which accept an endpoint address, to which you can pass the URL. Often, you will want to point to the service at the site of origin for the Silverlight application (i.e., the site from which the Silverlight application was downloaded). You can ascertain this URL using the Source
property of the Application.Current.Host
object.
There are a few pitfalls to be aware of when calling WCF Services in Silverlight. As RIA Services are built upon WCF, these issues also apply to it as well.
The WCF Service on the server has a limit to the maximum message size it will return as a response, and the Silverlight client also has a maximum message size that it will accept. Although the default maximum message size accepted by the Silverlight client is configured at an impossibly large 2 GB, the service in the Web project has a default maximum message size of 64 KB. Attempting to return a response larger than this from your service (common when returning large lists of data) will result in an exception being thrown. You can modify this limit by customizing your binding properties in the Web project's Web.config
file, setting an alternative maximum message size (in bytes). That said, it's probably better to design your server/client communication to transfer smaller messages where possible. Unless the user requires all that data at the one time, you would be better aiming to minimize data traffic, and hence the time that the user is left waiting for a response from the server, sending only the data back to the client that the user requires at that time. For example, when displaying a list of data to the user, request the data in pages rather than as a single unit.
Despite Silverlight enforcing that server calls must be performed asynchronously, if you have ASP.NET session state turned on in your Web project, you will find that calls are executed on the server sequentially, resulting in the calls appearing slower than expected.
You can tell if ASP.NET session state is turned on by using a tool such as Fiddler (which you can download from www.fiddler2.com
) to intercept the responses from the server. If a cookie with the name ASP.NET_SessionId
appears in the header, then ASP.NET session state is turned on. Generally, ASP.NET session state will be turned on automatically when you add a Global.asax
file to your project.
This behavior occurs only when using the browser networking stack, which is the default when the application is running inside the browser. To work around this behavior, you can either stop using ASP.NET session state or simply use the client networking stack instead (discussed in the section “Choosing a Networking Stack,” later in this chapter).