How to: Create and Access a WCF Service with the Channel Model
Microsoft Silverlight will reach end of support after October 2021. Learn more.
This topic describes how to create a Silverlight version 4 client that can access a Windows Communication Foundation (WCF) service operation asynchronously using a ChannelFactory-based client application. It also describes how to share code between a Silverlight client and the service it accesses.
Using channel-level programming on the client enables you to access a service without having to generate a proxy. Channel-level communication is done using the ChannelFactory class instead of a proxy. This approach provides an alternative to using tools like Add Service Reference and Slsvcutil.exe to generate a proxy. When using this approach, the Silverlight client code needs to manually specify a contract that is compatible with the service. This is usually done by providing types highlighted with the ServiceContractAttribute and DataContractAttribute attributes. Since ServiceReferences.ClientConfig is not automatically generated when using channel-level programming, the client also needs to manually specify the address of the service and a binding that is compatible with the configuration of the service. By doing this, the client manually satisfies the “ABC” (address, binding, and contract) requirements of the service (answering the where, how, and what questions) that enable communication between the client and service to occur.
When you have a rich set of types on the server side that you want to reuse in the Silverlight client application, the channel-level programming should be considered. You could, for example, have a set of data types (highlighted with the DataContractAttribute attribute) that have advanced data validation capabilities. If you were to use a proxy generation approach, the equivalent types generated on the Silverlight side would lose all of this validation functionality. They would simply become data containers. If you want to preserve this functionality, channel-level programming approach can so better. This topic shows one way to reuse both the service contract type on the server and the server data types on a Silverlight client: the code file that includes these types (IService1.cs) is simply included in both the server and client projects. There are also other possible mechanisms to use in this situation—for example, you can have separate source files for Silverlight and the server and then keep them in sync. However, any mechanism must be based on source code reuse and not on the binary assemblies: Silverlight cannot load assemblies that were compiled for the server-side .NET Framework.
This simple service contains information about a Person
, Name
, and Age
properties, which are returned when the Silverlight client invokes the GetPerson
method on the service.
Tip: |
---|
The full source code for each file is available at the end of the procedure. |
To create the Silverlight application
From the File menu, select New, then Project, then Silverlight from the Project types, and then select Silverlight Application from the Visual Studio installed templates. Accept the default NameSilverlightChannelApp1 and click OK.
In the New Silverlight Application wizard that pops up, accept the Host the Silverlight application in a new Web site default that is checked. Accept the default value SilverlightChannelApp1.Web for the New Web project name and the default ASP.NET Web Application Project as the New Web project type. Then select OK.
To create the Silverlight-enabled service
To add a Silverlight-enabled Windows Communication Foundation (WCF) service to the default Web application generated, right-click the SilverlightApplication1Web project in the Solution Explorer and select Add, then New Item…. Select Silverlight from the Categories on the left side of the Add New Item window, then select the Silverlight-enabled WCF Service from the Visual Studio installed templates on the right, accept the default value of Service1.svc in the Name box, and click Add.
To define the service contract with an interface that can be shared with the client, right-click the SilverlightApplication1Web project in the Solution Explorer and select Add, then New Item…. Select Code from the Categories on the left side of the Add New Item window, then select the Code File from the Visual Studio installed templates on the right, accept the name of the file to IService1.svc in the Name box, and click Add.
The following code defines the service contract with the IService1 interface in the IService1.cs file, and specifies (with the
#if SILVERLIGHT
directive) that Silverlight clients must use the asynchronous invocation pattern for calling theGetPerson
method. This code should be pasted into the IService1.cs file just created in the SilverlightApplication1Web project.// The IService1.cs file. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace SilverlightChannelApp1.Web { // NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config. [ServiceContract] public interface IService1 { #if SILVERLIGHT [OperationContract(AsyncPattern=true)] IAsyncResult BeginGetPerson(int personId, AsyncCallback callback, object state); Person EndGetPerson(IAsyncResult result); #else [OperationContract] Person GetPerson(int personId); #endif } [DataContract] public class Person { [DataMember] public string Name; [DataMember] public int Age; } }
This contract shows how to include a synchronous invocation pattern for non-Silverlight clients that might need to call the
GetPerson
method synchronously.The new location of the contract in the IService.cs file must be referenced in the configuration of the service. Right-click the Web.Config file in the SilverlightApplication1Web project in the Solution Explorer and select Open. Locate the correct service endpoint element in the <system.serviceModel> element near the bottom of the file, comment out the <serviceHostingEnvironment> element, and change the value of the contract attribute of the <endpoint> to "SilverlightChannelApp1.Web.IService1". The following code sample shows the configuration elements altered, but not all elements.
<system.serviceModel> <!--serviceHostingEnvironment aspNetCompatibilityEnabled="true" /--> <!-- Commented out.--> … <endpoint address="" binding="customBinding" bindingConfiguration="customBinding0" contract="SilverlightChannelApp1.Web.IService1" /> <!-- Attribute value changed.--> … <system.serviceModel>
The service implements the IService1 interface. It simply returns a
Person
when theGetPerson
method is called. To implement the service, right-click the Service1.svc.cs file in the SilverlightApplication1Web project in the Solution Explorer, select Open, and replace the code there with the following code.using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace SilverlightChannelApp1.Web { // NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config. public class Service1 : IService1 { public Person GetPerson(int personId) { return new Person { Name = "Brad", Age = 54 }; // } } }
To create the Silverlight client
To define the service contract for the Silverlight client, right-click the SilverlightChannelApp1 project in the Solution Explorer and select Add, then Existing Item…. Select the SilverlightChannelApp1.Web directory from the Folders on the left side of the Add Existing Item window, then select the IService1.cs file on the right of the window from this directory. To share the file by reference, accept the default File name of the file to IService1.cs in the File name box, click the drop-down menu on the Add button, and select Add As Link.
To add references to the relevant Silverlight 4 assemblies, right-click the SilverlightChannelApp1 project in Solution Explorer and select Add Reference…. Click the .NET tab, select the System.ServiceModel.dll and System.ServiceModel.dll assemblies, and click OK.
To implement the client application, open the MainPage.xaml.cs from the SilverlightChannelApp1 project and add the following using statements.
using System.ServiceModel; using SilverlightChannelApp1.Web; using System.ServiceModel.Channels; using System.Threading;
Replace the code in the SilverlightChannelApp1 namespace with the following code. The UI thread must be created as callbacks in Silverlight are sent on a worker thread and must be used to update the UI thread when received from the service. When the
Button_Click
event occurs, the CustomBinding is created, and the ChannelFactory is created and used to open the channel that is used to invoke theGetPerson
asynchronously.Caution: You must replace the port number in the URL for the endpoint address with the one generated when you view the service in the browser. Right-click the SilverlightChannelApp1.Web project and select View in Browser. Note the port number that appears for the service and paste it into the URL contained in the EndpointAddress in the following code sample. namespace SilverlightChannelApp1 { public partial class MainPage : UserControl { // Declare a separate UI thread. SynchronizationContext uiThread; public MainPage() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { // Create a custom binding that uses HTTP and binary encoding. var elements = new List<BindingElement>(); elements.Add(new BinaryMessageEncodingBindingElement()); elements.Add(new HttpTransportBindingElement()); var binding = new CustomBinding(elements); // Create a channel factory for the service endpoint configured with the custom binding. var cf = new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost:29204/Service1.svc")); //You must replace the port number with your own. // Save the synchronized context for the UI thread. uiThread = SynchronizationContext.Current; // Open the channel. IService1 channel = cf.CreateChannel(); // Invoke the method asynchronously. channel.BeginGetPerson(4, GetPersonCallback, channel); } // Implement the callback. void GetPersonCallback(IAsyncResult asyncResult) { Person p = ((IService1)asyncResult.AsyncState).EndGetPerson(asyncResult); //Update the UI thread and its output with the result of the method call. uiThread.Post(UpdateUI, p); } // Update the result displayed for the GetPerson invocation. private void UpdateUI(object state) { Person p = (Person)state; resultPersonName.Text = "The name of the Person is: " + p.Name; resultPersonAge.Text = "The age of the Person is: " + p.Age; } } }
When the callback returns, the UI threat is updated and the
Person
result posted to the browser.To define
resultPersonName
andresultPersonAge
and display the result of the Web service call in a client control, open the MainPage.xaml file and add a StackPanel with two TextBlock controls to the Grid element.<StackPanel> <Button Name="ok" Content="Click to get Person" Click="Button_Click"></Button> <TextBlock Name="resultPersonName">Name goes here.</TextBlock> <TextBlock Name="resultPersonAge">Age goes here.</TextBlock> </StackPanel>
The control is now ready to use. Press CTRL + F5 in Visual Studio 2010 to open the SilverlightChannelApp1TestPage.aspx client page, which is automatically generated to test the Silverlight 4 control. Click the Click to get Person button to see the name and age of the person retrieved.
Example
The pages for the sample are shown below.
//The IService1.cs page.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace SilverlightChannelApp1.Web
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.
[ServiceContract]
public interface IService1
{
#if SILVERLIGHT
[OperationContract(AsyncPattern=true)]
IAsyncResult BeginGetPerson(int personId, AsyncCallback callback, object state);
Person EndGetPerson(IAsyncResult result);
#else
[OperationContract]
Person GetPerson(int personId);
#endif
}
[DataContract]
public class Person
{
[DataMember]
public string Name;
[DataMember]
public int Age;
}
}
-----------------------------------------------------------------------------------------
//The Service1.svc.cs page
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace SilverlightChannelApp1.Web
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config.
public class Service1 : IService1
{
public Person GetPerson(int personId)
{
return new Person { Name = "Brad", Age = 54 }; //
}
}
}
-------------------------------------------------------------------------------------------
//The Web.config page
<?xml version="1.0"?>
<configuration>
…
<!—This contains the <system.serviceModel> element only.-->
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SilverlightChannelApp1.Web.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="customBinding0">
<binaryMessageEncoding />
<httpTransport />
</binding>
</customBinding>
</bindings>
<!--serviceHostingEnvironment aspNetCompatibilityEnabled="true" /-->
<services>
<service behaviorConfiguration="SilverlightChannelApp1.Web.Service1Behavior"
name="SilverlightChannelApp1.Web.Service1">
<endpoint address=""
binding="customBinding"
bindingConfiguration="customBinding0"
contract="SilverlightChannelApp1.Web.IService1" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
-------------------------------------------------------------------------------------------
//The MainPage.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 SilverlightChannelApp1.Web;
using System.ServiceModel.Channels;
using System.Threading;
namespace SilverlightChannelApp1
{
public partial class MainPage : UserControl
{
//Declare a separate UI thread.
SynchronizationContext uiThread;
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Create a custom binding that uses HTTP and binary encoding.
var elements = new List<BindingElement>();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);
// Create a channel factory for the service endpoint configured with the custom binding.
var cf = new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost:29204/Service1.svc"));
// Save the synchronized context for the UI thread.
uiThread = SynchronizationContext.Current;
//Open the channel.
IService1 channel = cf.CreateChannel();
// Invoke the method asynchronously.
channel.BeginGetPerson(4, GetPersonCallback, channel);
}
// Implement the callback.
void GetPersonCallback(IAsyncResult asyncResult)
{
Person p = ((IService1)asyncResult.AsyncState).EndGetPerson(asyncResult);
// Update the UI thread and its output with the result of the method call.
uiThread.Post(UpdateUI, p);
}
// Update the result displayed for the GetPerson invocation.
private void UpdateUI(object state)
{
Person p = (Person)state;
resultPersonName.Text = "The name of the Person is: " + p.Name;
resultPersonAge.Text = "The age of the Person is: " + p.Age;
}
}
}
-------------------------------------------------------------------------------------------
//The MainPage.xaml
<UserControl x:Class="SilverlightChannelApp1.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<Button Name="ok" Content="Click to get Person" Click="Button_Click"></Button>
<TextBlock Name="resultPersonName">Name goes here.</TextBlock>
<TextBlock Name="resultPersonAge">Age goes here.</TextBlock>
</StackPanel>
</Grid>
</UserControl>
Security
For information on cross-domain access, see Making a Service Available Across Domain Boundaries.
See Also
Send comments about this topic to Microsoft.
Copyright © 2010 by Microsoft Corporation. All rights reserved.