This topic describes how to access a service from a Silverlight client using a proxy. A proxy is a class that helps you access a particular service, and is generated automatically using the Add Service Reference tool in Visual Studio 2008. You must generate a separate proxy for each service you want to access.
This topic assumes that you have already built a Silverlight-enabled Windows Communication Foundation (WCF) service by completing the procedures in the topic How to: Build a Service for Silverlight Clients.
The same procedure outlined here can also be used to access a public SOAP service, if you know the service address, and if the service allows access by browser applications from your application’s domain. For more information, see Accessing SOAP Services and Making a Service Available Across Domain Boundaries.
Some public services, such as plain HTTP or REST services, do not publish a computer-readable description of themselves, and therefore a proxy cannot be automatically generated for them. These services cannot be accessed by using the steps described here. Instead, for procedures on how to access them, see Accessing HTTP-Based Services Directly.
The steps for these tasks are described in the How to: Build a Service for Silverlight Clients procedure.
Right-click the SilverlightApplication1 project in Solution Explorer and select Add Service Reference.
Click the Discover button to find CustomerService.svc created in the How to: Build a Service for Silverlight Clients procedure. It should appear in the Services: box.
Accept the ServiceReference1 in the Namespace field and click OK.
Notice that Solution Explorer now has added a folder called Service References. You can explore ServiceReference1 in that folder by right-clicking and selecting View in Object Browser. Note that it contains the SilverlightApplication1.ServiceReference1.CustomerServiceClient class and its methods. These are methods invoked to call the service.
Go to the Page.xaml.cs (code-behind) file in the client application and add the following using statements at the top of the page.
using System.ServiceModel; using SilverlightApplication1.ServiceReference1;
You must instantiate the Web service proxy before using the service. For example, you can do this in the Page() constructor.
Page()
CustomerServiceClient proxy = new CustomerServiceClient();
If you are unsure where to place code within the Page.xaml.cs file, in this step or in the following steps, consult the sample code summarized for this code-behind page at the bottom of this procedure.
Note that by default, configuration information for the proxy (such as the Web service address) is read from the ServiceReferences.ClientConfig file. This file is automatically created by the Add Service Reference tool. For more information, see Configuring Web Service Usage in Silverlight Clients.
All Web service calls in Silverlight are asynchronous. The proxy contains two members for each operation in the service: an asynchronous method and a completed event. For example, consider the CountUsers service operation. We first add an EventHandler to CountUsersCompleted – this event will be invoked when the service returns the data we requested. After the event is set up, we are ready to make the call to the service by calling CountUsersAsync.
CountUsers
EventHandler
CountUsersCompleted
CountUsersAsync
proxy.CountUsersCompleted += new EventHandler<CountUsersCompletedEventArgs>(proxy_CountUsersCompleted); proxy.CountUsersAsync();
The event handler specifies that the proxy_CountUsersCompleted method should be called when the service returns some data. We need to define and add this method in the Page class.
proxy_CountUsersCompleted
Page
void proxy_CountUsersCompleted(object sender, CountUsersCompletedEventArgs e) { userCountResult.Text = "Number of users: " + e.Result; }
Follow a similar procedure to call the second GetUsers operation offered by the service. The complete code can be found at the end of this topic.
GetUsers
To display the result of the Web service calls in a client control, open the Page.xaml file and add a StackPanel with two TextBlock controls to the Grid element.
<StackPanel> <TextBlock x:Name="userCountResult" /> <TextBlock x:Name="getUserResult" /> </StackPanel>
The control is now ready to use. Press Ctrl + F5 in Visual Studio 2008 to open the CustomerClientTestPage.aspx client page, which is automatically generated to test the Silverlight 2 control. You should see the lines “Number of users: 2” and “User name: Paul, age: 24, is member: True” in the browser window on the test page.
The following additional notes clarify and extend the procedure outlined above.
Various errors may occur when calling services. For example, the service may not be available, it may not be configured to support protocols understood by Silverlight, or the service address may be wrong. Errors may also occur due to cross-domain access issues. The service you are attempting to connect to may not publish an appropriate cross-domain policy, and so access from browser-based applications may not be allowed. For more information, see Making a Service Available Across Domain Boundaries.
In another specific class of errors, there may be an error specific to a particular service that is sent as a SOAP Fault. This could be the case, for example, if you attempted to call GetUser with a non-existent user ID. Note that Silverlight does not let you distinguish between these errors or read SOAP Fault details, but you may still handle all these errors in the same way.
GetUser
To handle error conditions, you must detect the error by checking the Error property on the resulting event arguments, before accessing the Result property. If you try accessing the Result property when an error condition has occurred, an exception will be thrown. The following example shows how to make the event handler from the earlier example in this topic robust against errors.
void proxy_CountUsersCompleted(object sender, CountUsersCompletedEventArgs e) { if (e.Error != null) { userCountResult.Text = "Number of users: " + e.Result; } else { userCountResult.Text = “Error getting the number of users”; } }
For more information on debugging error conditions, see Debugging Services for Silverlight Applications.
The types generated by the Add Service Reference tool are designed to make data binding scenarios easy. Most types implement the INotifyPropertyChanged interface, so that any updates to instances of these types will automatically update UI components. For the same reason, most collections are returned from services as instances of the generic ObservableCollection class by default. For more information, see the Data Binding section of the Silverlight documentation.
As mentioned before, the default proxy constructor configures the proxy based on information in the ServiceReferences.ClientConfig file.
The configuration file contains the service address and binding information. Binding information informs Silverlight about the protocol details used to communicate with the service, such as the appropriate character encoding.
The configuration file may consist of multiple <endpoint> elements for the same service. This allows you to store multiple configurations for the same service and select one at runtime. This approach is described in the Configuring Web Service Usage in Silverlight Clients topic.
Occasionally, you may want to eliminate the configuration system altogether, and specify the address and binding information in code. This is possible by using an appropriate proxy constructor.
CustomerServiceClient proxy = new CustomerServiceClient(new BasicHttpBinding(), new EndpointAddress("http://services.contoso.com/CustomerService.svc"));
The default BasicHttpBinding binding will be appropriate for most scenarios, but may also be customized.
Usually, the event-based asynchronous model described previously raises the completion event on the same thread on which the service was called. This is convenient in many applications, since you often invoke services from the UI (User Interface) thread, and can update UI components (such as text boxes in our example) directly in the completion event handler.
Occasionally, you may want the completion event to be processed on a background thread. Either for this or for other reasons, you may want to use an alternative asynchronous invocation model based on the IAsyncResult mechanism and on Begin/End methods.
To use this model, you must first cast the proxy to an appropriate interface type. The interface type is generated automatically alongside the proxy by the Add Service Reference tool. You can then invoke the appropriate Begin method.
IAsyncResult iar = ((CustomerService)proxy).BeginGetUser(userId, GetUserCallback, proxy);
The last two parameters are the callback method to invoke (on a background thread) when the service call completes, and the proxy. The proxy is passed as a state object so that the callback method will be able to call the corresponding End method. You then need to implement the callback and call the appropriate End method.
static void GetUserCallback(IAsyncResult iar) { User u = ((CustomerService)iar.AsyncState).EndGetUser(iar); string name = u.Name; // Use returned data ... }
If an error occurs while invoking the service, the End method will throw an exception.
The following is the summarized code of Page.xaml.cs after completing all of the preceding procedures.
//Page.xaml.cs using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ServiceModel; using SilverlightApplication1.ServiceReference1; namespace SilverlightApplication1 { public partial class Page : UserControl { public Page() { // Required to initialize variables InitializeComponent(); CustomerServiceClient proxy = new CustomerServiceClient(); proxy.CountUsersCompleted += new EventHandler< CountUsersCompletedEventArgs>(proxy_CountUsersCompleted); proxy.CountUsersAsync(); proxy.GetUserCompleted += new EventHandler< GetUserCompletedEventArgs>(proxy_GetUserCompleted); proxy.GetUserAsync(1); } void proxy_GetUserCompleted(object sender, GetUserCompletedEventArgs e) { getUserResult.Text = "User name: " + e.Result.Name + ", age: " + e.Result.Age + ", is member: " + e.Result.IsMember; } void proxy_CountUsersCompleted(object sender, CountUsersCompletedEventArgs e) { if (e.Error != null) { userCountResult.Text = "Number of users: " + e.Result; } else { userCountResult.Text = “Error getting the number of users”; } } } }
1/7/2008
The error handling code was added to the sample and the alert about errors when using the Visual Studio debugger.
Confirmation that this feature was working correctly was not obtained until after Silverlight 2.0 RTM, so the documentation of it was delayed until now.