New Web Services Features in Silverlight 2 RTW

Here is an overview of the new features added since Beta 2.

SOAP Headers

SOAP headers were a big customer request, so we have added headers support in this release. There are two methods to use headers, depending on the customer scenario.

The easier method is useful when you need to get/set a header while invoking a single operation on the service.  You can use the OperationContext class, and specifically the IncomingMessageHeaders or OutgoingMessageHeaders property to receive or send a header. Below is a code example where the client sends the header <MyClientHeader xmlns="https://silverlight.net">...</MyClientHeader> to the service.

public Page()
{

     InitializeComponent();

     // Instantiate generated proxy
Service1Client proxy = new Service1Client();

     // Hook up an event handler when operation completes
proxy.DoWorkCompleted += new EventHandler<DoWorkCompletedEventArgs>(proxy_DoWorkCompleted);

     using (OperationContextScope ocs = new OperationContextScope(proxy.InnerChannel))
{

     // We can add "MyClientHeader" SOAP header to the outgoing message. It is important that this comes before calling the operation.
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("MyClientHeader", "https://silverlight.net", "..."));

     // Call an operation on the service
     proxy.DoWorkAsync("foo");

     }
}

Now assume we want to get at a header that the service sent back. In this example, the service sent the <MyServiceHeader xmlns="https://silverlight.net">...</MyServiceHeader> header to the client. Note: The old Begin*/End* async pattern needs to be used with OperationContext if you are retrieving the headers sent in a service reply. The event-based async pattern (using *Completed event and *Async method) is currently not supported. Also, please be sure to use exact order of calls demonstrated below.

public Page()
{

InitializeComponent();

     // Instantiate generated proxy
     Service1Client proxy = new Service1Client();

     // Call an operation on the service using Begin/End async pattern
((Service1)proxy).BeginDoWork("foo", doWorkCallback, proxy);

}

// Receive callback when the service responds
void doWorkCallback(IAsyncResult result)
{
     Service1 proxy = (Service1)result.AsyncState;

     using (OperationContextScope ocs = new OperationContextScope(((Service1Client)proxy).InnerChannel))
{

          // Get the service response. This needs to happen before we access the headers.
          string response = proxy.EndDoWork(result);

          // Now we can access the headers of the incoming message. In this case we assume the header "MyServiceHeader" was sent by the service.
          string headerContents = OperationContext.Current.IncomingMessageHeaders.GetHeader<string>("MyServiceHeader", "https://silverlight.net");

          // Do something with response and header

}
}

The harder method can be used when you want to get/set headers upon invoking any operation on the service. In that case you would have to use OperationContext every time you call a service operation, which is very clunky. Instead, what would be useful is functionality similar to that provided by WCF message inspectors and the IClientMessageInspector class. Using a client message inspector you can get access to the Message instance just received from or about to be sent to the service, and manipulate the headers directly on the instance. IClientMessageInspector is not part of SL 2, but this code sample demonstrates how to accomplish the same functionality. In the sample, we add a header to every message sent to the service, and the header contains the IP address of the client which is calling the service.

SOAP Faults

Eugene posted about faults just a couple of weeks back. Faults are another major request but unfortunately they are not supported out of the box in SL 2. However, using the same client message inspectors approach and some extra configuration on the WCF service side, rudimentary faults can be enabled. The same code sample linked above demonstrates how to do this. I will put together a more detailed post about the sample in the coming weeks.

Reusing existing types in "Add Service Reference"

If you use "Add Service Reference" to add a reference to the service in your client, it will by default generate a duplicate of any type used by your service operation. Frequently when developing a WCF service and a Silverlight client, you want to declare your types in only one place. Alternatively, maybe your data contract types contain internal logic that you would like to preserve and that logic is lost when "Add Service Reference" generates the duplicate types in the client.

Reusing types in Add Service Reference

To prevent duplicate types from being generated in the client, you would normally declare the data contract types in a new project, which is referenced by both your WCF service and the Silverlight client. Then when adding a reference to the service, you would select the correct option in the "Add Service Reference" dialog. This way "Add Service Reference" would find the types already referenced in the shared project, and not generate duplicates.

Note:  In Silverlight the shared project that contains the data contract types has to be a "Silverlight Library", which can be referenced by both Silverlight projects and regular desktop framework projects. If the shared project is of any other type, you will not be able to reference it from within a "Silverlight Application" project.

This option was already there in the Silverlight version of "Add Service Reference", but it did not work up until this release.

Duplex Services

We have made improvements to the duplex channel shipped in Beta 2:

  • Client and server side duplex components are now ready for production use, with improved stability, security, etc. In Beta 2 the server-side duplex component was under an "evaluation" license.
  • Server-side duplex component may now be hosted in Partial Trust
  • Default timeouts and timeout behavior is “smarter” – no changes should be required for common scenarios

In addition, Eugene has developed a great sample on top of the raw duplex channel model, to simplify duplex usage. On the client side you can use the DuplexReceiver class:

public class DuplexReceiver
{

     // Opens a channel to the service to receive messages using the default PollingDuplexBinding
     public DuplexReceiver(Uri address, IEnumerable<Type> possibleTypes);

     // Opens a channel to the service to receive messages using the default PollingDuplexBinding
public DuplexReceiver(EndpointAddress address, IEnumerable<Type> possibleTypes);

     // Sends a message to a Duplex service. (The first time a message is sent, connection will be established to the service).
public void SendMessageAsync(object message);

     // Event that will be called after a message has been sent
public event EventHandler<AsyncCompletedEventArgs> SendMessageCompleted;

     // Event that will be called for each received message
public event EventHandler<ReceivedMessageEventArgs> MessageReceived;

     // Disconnects from a Duplex service (can later reconnect again). To permanently close, first call DisconnectAsync and then call Close
public void DisconnectAsync();

     // Closes the Duplex connection
public void Close();

     // Other methods and properties omitted for brevity...

}

Instead of using the channel model directly, you can use the SendMessageAsync method to send messages to the service. The MessageReceived event is fired when the service sends the client a message. Note that the DuplexReceiver contructor takes the list of types that can possibly be sent. The sample also provides a server-side DuplexService object, which keeps track of connected clients and can relay messages to the correct recipient, or broadcast messages to all clients.

Support for ChannelFactory<T>

This is an advanced use case: say you want to use a service without generating a proxy to it. Perhaps you are connecting to a complex service and you manually copied over/shared all the contracts and types that the service uses (for example in order to preserve the internal logic of these types). But you never used the "Add Service Reference" wizard so you don't have a proxy class to the service. In this case ChannelFactory<T> can be used to communicate with a service which implements the service contract <T>, as shown below. 

public Page()
{
InitializeComponent();

     ChannelFactory<Service1Channel> factory = new ChannelFactory<Service1Channel>(
          new BasicHttpBinding(),
          new EndpointAddress("https://localhost:49507/Service1.svc"));

     Service1Channel channel = factory.CreateChannel();

channel.BeginDoWork(

new AsyncCallback(doWorkCallback), channel);
}

void doWorkCallback(IAsyncResult result)
{
     Service1Channel channel = (Service1Channel)result.AsyncState;
     string response = channel.EndDoWork(result);

     // Do something with response
}

Note that using this approach, the event-based async pattern is not supported.

Other

  • Types generated from ADO.NET Entity Data Models (.edmx files)  in WCF services intended for Silverlight consumption are now supported. Just use the type generated from a .edmx model as an argument in a service operation.
  • Internal types and internal members may now be serialized by the DataContractSerializer and the DataContractJsonSerializer (on a per-assembly opt-in basis)
  • JSON/XML Mapping is now supported (through System.Runtime.Serialization.Json.dll in the SDK), by using the JsonReaderWriterFactory class.
  • JAXB-style object references in XML are now supported by DataContractSerializer

Have fun using our new feature set!

Yavor Georgiev
Program Manager, Connected Framework Team