In Silverlight3 è stato semplificato il meccanismo di fruizione di servizi WCF che usano la modalità duplex, nella Beta1 c’è in realtà ancora parecchio lavoro da fare ma con la RTM il tutto si ridurrà nel aggiungere un riferimento al servizio WCF duplex e gestire l’evento generato quando il servizio ha delle informazioni da comunicare all’applicazione Silverlight.
Per darvi un idea, ecco un esempio di servizio WCF che ‘pusha’ dei valori quando questi hanno un valore inferiore ad una soglia prefissata:

L’interfaccia del servizio è:

   1: [ServiceContract(CallbackContract = typeof(ICallback))]
   2: interface IMyDuplexService
   3: {
   4:     [OperationContract(IsOneWay = true)]
   5:     void RegisterThreshold(int thresholdLevel);
   6:  
   7:     [OperationContract(IsOneWay = true)]
   8:     void Unregister();
   9: }

mentre quella di callback è:

   1: [ServiceContract()]
   2: interface ICallback
   3: {
   4:     [OperationContract(IsOneWay = true)]
   5:     void ValueChanged(int newValue);
   6: }

L’implementazione del servizio è veramente banale:

   1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   2: public class MyDuplexService : IMyDuplexService
   3: {
   4:     private int thresholdLevel;
   5:     ICallback callback;
   6:     Random rnd;
   7:     Timer timer;
   8:  
   9:     public void RegisterThreshold(int thresholdLevel)
  10:     {
  11:         this.thresholdLevel = thresholdLevel;
  12:         callback = OperationContext.Current.GetCallbackChannel<ICallback>();
  13:         rnd = new Random();
  14:         timer = new Timer(o =>
  15:             {
  16:                 int newValue = this.rnd.Next(0, 100);
  17:                 if (newValue <= this.thresholdLevel)
  18:                     callback.ValueChanged(newValue);
  19:             }, null, 1000, 1000);
  20:     }
  21:  
  22:     public void Unregister()
  23:     {
  24:         this.timer.Change(Timeout.Infinite, Timeout.Infinite);
  25:     }
  26: }

A questo punto, con la beta1 è necessario creare una custom service host factory che inserisce tra i bindings utilizzati dall’hoster del nostro servizio un PollingDuplexBinding il quale attiva la modalità di smart polling lato client da usare in tutti quei contesti dove non è possibile aprire un canale di comunicazione verso il chiamante del servizio. Il codice della custom host factory è il seguente:

   1: public class PollingDuplexServiceHostFactory : ServiceHostFactoryBase
   2: {
   3:     public override ServiceHostBase CreateServiceHost(string constructorString,
   4:          Uri[] baseAddresses)
   5:     {
   6:         return new PollingDuplexSimplexServiceHost(baseAddresses);
   7:     }
   8: }
   9:  
  10: class PollingDuplexSimplexServiceHost : ServiceHost
  11: {
  12:     public PollingDuplexSimplexServiceHost(params System.Uri[] addresses)
  13:     {
  14:         base.InitializeDescription(typeof(MyDuplexService), new UriSchemeKeyedCollection(addresses));
  15:         base.Description.Behaviors.Add(new ServiceMetadataBehavior());
  16:     }
  17:  
  18:     protected override void InitializeRuntime()
  19:     {
  20:         // Add an endpoint for the given service contract.
  21:         this.AddServiceEndpoint(
  22:              typeof(IMyDuplexService),
  23:              new CustomBinding(
  24:                   new PollingDuplexBindingElement(),
  25:                   new BinaryMessageEncodingBindingElement(),
  26:                   new HttpTransportBindingElement()),
  27:                   "");
  28:  
  29:         // Add a metadata endpoint.
  30:         this.AddServiceEndpoint(
  31:              typeof(IMetadataExchange),
  32:              MetadataExchangeBindings.CreateMexHttpBinding(),
  33:              "mex");
  34:  
  35:         base.InitializeRuntime();
  36:     }
  37: }

ovviamente è necessario modificare anche il file .svc

   1: <%@ ServiceHost Language="C#" Debug="true" Service="SL3DuplexWS.Web.MyDuplexService"  Factory="SL3DuplexWS.Web.PollingDuplexServiceHostFactory" CodeBehind="MyDuplexService.svc.cs" %>
 
A questo punto il nostro servizio WCF duplex è pronto per essere consumato da un applicazione Silverlight.

La UI del client è veramente banale:
   1: <UserControl x:Class="SL3DuplexWS.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     Width="400" Height="300">
   5:     <StackPanel x:Name="LayoutRoot" Background="White">
   6:         <TextBox x:Name="txtThreshold" Margin="10" Text="50"/>
   7:         <Button Margin="10" Content="Start" Click="HandleRegister" />
   8:         <Button Margin="10" Content="Stop" Click="HandleUnregister" />
   9:         <TextBlock x:Name="txtValue" FontSize="48" Foreground="Red" HorizontalAlignment="Center" Text="-" />
  10:     </StackPanel>
  11: </UserControl>
 
Aggiungiamo un service reference al servizio WCF ottenendo così la creazione della parte proxy e dei relativi tipi esposti dal nostro servizio, nella beta1 c’è un bug percui il file ServiceReference.ClientConfig viene generato vuoto, per ora è perciò necessario inizializzare il proxy manualmente da codice:
   1: public MainPage()
   2: {
   3:     InitializeComponent();
   4:     //Proxy initialization
   5:     EndpointAddress address = new EndpointAddress("http://localhost:2768/MyDuplexService.svc");
   6:     CustomBinding binding = new CustomBinding(
   7:          new PollingDuplexBindingElement(),
   8:          new BinaryMessageEncodingBindingElement(),
   9:          new HttpTransportBindingElement());
  10:     //Creates proxy
  11:     proxy = new SL3DuplexWS.localhost.MyDuplexServiceClient(binding, address);
  12:     //*new* callbacks are exposed as events
  13:     proxy.ValueChangedReceived += new EventHandler<ValueChangedReceivedEventArgs>(proxy_ValueChangedReceived);
  14: }
 
fatto questo, completiamo l’opera inserendo le chiamate al servizio e la gestione dell’evento ValueChangedReceived:
   1: private void proxy_ValueChangedReceived(object sender, ValueChangedReceivedEventArgs e)
   2: {
   3:     txtValue.Text = e.newValue.ToString();
   4: }
   5:  
   6: private void HandleRegister(object sender, RoutedEventArgs e)
   7: {
   8:     proxy.RegisterThresholdAsync(int.Parse(txtThreshold.Text));
   9: }
  10:  
  11: private void HandleUnregister(object sender, RoutedEventArgs e)
  12: {
  13:     proxy.UnregisterAsync();
  14: }

That’s all! tutto il lavoro infrastrutturale per gestire la duplice comunicazione (smat polling, marshaling…) è interamente gestito da Silverlight3 smile_regular