Using Silverlight 4 and Net.TCP Duplex Callbacks

Over the last several weeks I've been investigating for a client a means of providing callbacks to Silverlight clients to push trade updates to all the users.  This has typcially been done using a PollingDuplexHttpBinding in Silverlight 3 and 2, but this is problematic as it is first a polling model (yuk!), and second the only data format is XML it could be slower then using a binary format.

The experience has been quite an adventure, as usual with beta Microsoft stuff.  There's not much documentation available, and everything that can be found is kind of all over the place in quality.  The best two sources I have found are the following two great blog posts by Tomasz Janczuk and Radenco Zec:

While these were valuable posts, I did still find the effort to have a lot of troubles, so I thought I'd also throw out my $0.02 on the process.

To get this Net.TCP to Silverlight 4 to work, you are going to need to do a bunch of things even to get up and running:

  • You need to run the web solution under IIS as the VS.NET web server does not support NET.TCP
  • You must configure Non-HTTP activation for IIS so it can respond to Net.TCP request
  • You must configure both the web site and web application to respond to Net.TCP
  • You must provide a policy server to verify the client as being able to communicate to the server with TCP

I had some problems with each of these, and I'll walk through configuration of these items.

Once you have that complete, there is also a bunch of configuration and programming that you need to get right in order for everything to work:

  • Your server web.config needs to be configured properly to support Net.TCP
  • Your client must also bind properly to the service

Yes, all WCF things need this, but I found documentation to be vexing, so I'll show exactly how I did it.

To get IIS configured to use Net.TCP, you need to go to control panel / install windows components, and make sure the following is selected:

This allows .NET to integrate with the Windows Process Activation Service to kick-start IIS on receipt of Net.TCP requests.  After installing this you will notice in the service management console the following service:

Make sure this service is running or you will get the an error when attempting to contact the service (I'll show the error later, once I get to some code).

Ok, A little of the prerequisites are out of the way so lets do a little coding!  Create a new visual studio solution which consists of a Silverlight 4 application and web site to serve the application.   My example looks like this:

At this point, because we cant use the VS.NET web server, we need to modify the web site configuration to run under IIS.  To do this, right click the web solution and bring up properties / web, and select "Use Local IIS Web server".  It will auto fill a project url, and then press 'Create Virtual Directory' to set up the application in IIS:

You should get a dialog stating that the virtual directory was created successfully.  Now add a WCF service to the web project that we will use to send messages back to the client.  I've called mine "PushDataService", and it creates an interface file (IPushDataService.cs) and PushDataService.svc file in the web project.  It is also needed to create an interface that represents the API that the server uses to talk back the the client, so I add an interface to the web solution named IPushDataCallback.

Now that we have the service and interfaces created, we need to add some method definitions to them and annotate them with attributes properly.  My IPushDataService interface is as follows:

namespace DuplexSilverlightExamples.Web
{
    [ServiceContract(CallbackContract = typeof(IPushDataCallback))]
    public interface IPushDataService
    {
        [OperationContract(IsOneWay = true)]
        void RegisterForUpdates();
        [OperationContract(IsOneWay = true)]
        void UnregisterForUpdates();
    }
}

The interface contains two methods, one that the client will call to notify the server that it wants to receive push updates, and another to tell the server would like to stop receiving updates.  Both of these methods are annotated with [OperationContract(IsOneWay = true)] which tells WCF that the client can call these methods and that there is no return value and it doesn't need to worry about a back channel.

The [ServiceContract(CallbackContract = typeof(IPushDataCallback))] on the interface tells WCF that the service implementing this interface is a WCF service and that it will support a communications channel back to the client and that it will follow the interface specified by IPushDataCallback.  And speaking of IPushDataCallback, here it is:

namespace DuplexSilverlightExamples.Web
{
    public interface IPushDataCallback
    {
        [OperationContract(IsOneWay = true)]
        void Update(string theUpdateMessage);
    }
}

This tells WCF that the communication channel back to the client will support a one way communications channel for passing a string message back to the client through the Update message.

Now we need to implement the service.  My service is implemented as such:

namespace DuplexSilverlightExamples.Web
{
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class PushDataService : IPushDataService
    {
        public static Dictionary<IPushDataCallback, IPushDataCallback> _clients =
            new Dictionary<IPushDataCallback, IPushDataCallback>();

        public PushDataService()
        {
            new Thread(new ThreadStart(pumpUpdates))
            {
                IsBackground = true
            }
            .Start();

        }

        public void RegisterForUpdates()
        {
            IPushDataCallback c = OperationContext.Current.GetCallbackChannel<IPushDataCallback>();
            if (!_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Add(c, c);
                }
            }
        }

        public void UnregisterForUpdates()
        {
            IPushDataCallback c = OperationContext.Current.GetCallbackChannel<IPushDataCallback>();
            if (_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Remove(c);
                }
            }
        }

        public void pumpUpdates()
        {
            Random w = new Random();

            List<IPushDataCallback> bad = new List<IPushDataCallback>();
            int count = 0;

            while (true)
            {
                Thread.Sleep(w.Next(10000));

                lock (_clients)
                {
                    if (_clients.Count > 0)
                    {
                        bad.Clear();
                        foreach (IPushDataCallback client in _clients.Keys)
                        {
                            try
                            {
                                client.Update(string.Format("{0}", count++));
                            }
                            catch (Exception)
                            {
                                bad.Add(client);
                            }
                        }

                        bad.ForEach(bc => _clients.Remove(bc));
                    }
                }
            }
        }
    }
}

The attributes on the class setup some Asp.NET compatibility, and also specify that I want a single instance of the service and that multiple clients can access it simultaneously.  The constructor starts a thread that runs the pumpUpdates method, and this will always run for the lifetime of the service, and since it is a singleton there will only be one of this tread running.  I also declare a dictionary object that will keep references to all connected clients that will be used to send the updates to the client.

The RegisterForUpdate method used the following statement:

IPushDataCallback c = OperationContext.Current.GetCallbackChannel<IPushDataCallback>();

What this does is it looks at the "context" of the current call - which contains information about various things, including any callback channels.  The GetCallbackChannel will look to see if there is an implementation of IPushDataCallback (which is specified on the IPushDataService definition, so it better be there).  If this client is not already in the dictionary, then the reference will be stored there for use by the pumpUpdates method.  The UnregisterForUpdates method basically does the opposite.

The pumpUpdates method will loop endlessly, sleeping for a random period up to 10 seconds, and then will send a message to all clients that are subscribed (the message is the count value).  Also on each pass through I keep track if there is an exception calling a specific client in the 'bad' list variable.  If an exception is caught, I'll remember that client and assume that it has "died" and that we don't want to call back to it anymore, and after all clients are updated I'll remove those "bad" clients from the update dictionary.

Ok, we've got the code in place.  Now we need to configure WCF (via the web.config) to support Net.TCP communication.  The configuration file as currently put together by vs.net is the following:


<?xml version="1.0"?>
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true"/>
    </system.webServer>

    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>

We need to extend this to the following:


<?xml version="1.0"?>
<configuration>

    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
 
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true"/>
    </system.webServer>

    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
             
              <behavior name="PushDataServiceBehavior">
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
                <serviceThrottling maxConcurrentCalls="2147483647" />
              </behavior>
             
            </serviceBehaviors>
        </behaviors>

      <bindings>
        <netTcpBinding>
          <binding name="InsecureTcp">
            <security mode="None"/>
          </binding>
        </netTcpBinding>
      </bindings>
     
      <services>
        <service behaviorConfiguration="PushDataServiceBehavior"
                 name="DuplexSilverlightExamples.Web.PushDataService">
          <endpoint address="" binding="netTcpBinding" bindingConfiguration="InsecureTcp"
                    contract="DuplexSilverlightExamples.Web.IPmtPocPushService"/>
          <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
          <host>
            <baseAddresses>
              <add baseAddress="net.tcp://localhost:4502/DuplexSilverlightExamples.Web/PushDataService.svc" />
            </baseAddresses>
          </host>
        </service>
      </services>
     
    </system.serviceModel>
</configuration>

What I've done here is added three sections:

  1. A service definition
  2. A netTcpBinding declaration
  3. and a new behavior section

The service definition:


<service behaviorConfiguration="PushDataServiceBehavior"
         name="DuplexSilverlightExamples.Web.PushDataService">
  <endpoint address="" binding="netTcpBinding" bindingConfiguration="InsecureTcp"
            contract="DuplexSilverlightExamples.Web.IPmtPocPushService"/>
  <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
  <host>
    <baseAddresses>
      <add baseAddress="net.tcp://localhost:4502/DuplexSilverlightExamples.Web/PushDataService.svc" />
    </baseAddresses>
  </host>
</service>

What this is specifying is that we want WCF to create a service implementing the IPushDataService interface, that we want to use the behavior we added in another section, the binding in the binding section, and that we want to expose two endpoints, one for metadata (using the mexTcpBinding), and one using NetTcpBinding (what this is all about) with the new behavior as well as the specified binding we are adding. 

Note that it is needed to add a base address entry to the service specifying the full address of the service.  Net.TCP doesn't seem to like that and you'll get and error if you dont use it.  Note also that I specify that the port used is 4502.  Silverlight is limited to ports 4502 - 4534, so you better specify one in that range.

The netTcpBinding is needed as we need to specify that there is no security to be used on the channel.  Net.TCP does not currently support a secure channel (they say this will likely be added in a future version) and we are forced to explicitly specify the insecurity.  The behavior section is also useful, mostly to specify that we want to have the maximum amount of simultaneous clients accessing the service.

Ok, we now have the server side complete, and we need to modify the client to use the service.  Add a service reference to the client application.  Or, well, try to, because you will likely get the following error:

Contract Requires Duplex, ...  WTF?  Well, this is a problem that I always get and expect you will too, and needs to be addressed.  Unfortunately, I spent a lot of time googling this to no avail.  How did I solve it?  I rebooted the computer and that problem went away.  Go figure!

However, like many things like this, that one problem begat another:

But this one is well known, and mentioned earlier.  We need to configure both the web site and the web application to accept net tcp connections.  First, configure the binding on the web site by right clicking the site and selecting edit bindings:

So, add the binding for net.tcp on port 4502:*  Then, it is also necessary to add net.tcp to the application, so right click on the application, select manage application -> advanced settings, and make sure that net.tcp is also specified instead of just http:

You should now be able to add the service reference to the client application.

The client application I put together shows the messages in a listbox; I won't go into that code.  But to utilize the service, I modify the MainPage.xaml.cs file as such:

public partial class MainPage : UserControl
{
    private ObservableCollection<string> _updates = new ObservableCollection<string>();

    public MainPage()
    {
        InitializeComponent();

        messagesListbox.ItemsSource = _updates;

        pds.PushDataServiceClient c =
            new pds.PushDataServiceClient("NetTcpBinding_IPushDataService");
        c.UpdateReceived += c_UpdateReceived;
        c.RegisterForUpdatesAsync();
    }

    void c_UpdateReceived(object sender, pds.UpdateReceivedEventArgs e)
    {
        if (e.Error == null)
        {
            _updates.Add(e.theUpdateMessage);
        }
    }
}

This is pretty straight forward code.  I keep an overvable collection of strings that hold the update messages and it is bound to the items source of the listbox.  I create an instane of PushDataServiceClient and pass the name of the binding in the ServiceReferences.ClientConfig file for the service (automatically created by the add service reference process).  The UpdateReceived event is bound to a method which will be called when the Update method on the client is called by the server (which triggers the UpdateReceived event - the name is the method name + "Received").  The event handler check to see if there was no error, and if so adds the parameter to the callback to the list, which then triggers an update of the UI.

But wait.  There is still a problem.  If you run this as is, it just looks like nothing is happening.  So, run the application in the debugger and you get the following error:

This is caused by us not having a socket policy server running.  When Silverlight attempts socket communications, it will contact the origin server on port 943 and ask for a policy file to see if the client is allowed to do communications.  This was actually a big issue for me at first as I thought I could just post the policy file in IIS and map port 943 to serve it with TCP.  That did not work.  You need to write your own server to do this, but thankfully there is one available as an online template project that you can add to your solution.  So, add a new project to the solution, but select "Online Templetes" for "silverlight tcp":

Once you have that project added, you can start it manually (it's a console app), or do like I do and setup multiple project starts with it going first.  Once you have it running you should see something like this in the application:

That is the listbox showing the messages sent from the server.  The console window just behind is the socket policy server.

But wait - there is perhaps one more problem!  If for some reason the Net.Tcp Listener service is not running, you would get an error similar to the following.  If that is the case, make sure the service is running.

Phew!  That's about it!  Must be a record for me for a blog post, but I hope it saves you a lot of hassle!

BTW, I will have a follow up post where I extend this service to have a binding for Http Duplex Polling in addition to the Net.TCP binding.  This will allow Silverlight 3 clients to still use the service, albeit a bit less efficiently, but it does keep them from being excluded from all the fun.  I'll write about how to do that soon.

blog comments powered by Disqus

About the author

I'm a .NET, XAML, and iOS polyglot that loves playing with new things and making cool and innovative stuff.  I am also a Mac junkie.

I am Principal Technologist for SunGard Global Services in NYC, in their Advanced Technologies practice, and I work extensively with SunGard's energy and financial customers.

Note the the posting on this blog are my own and do not represent the position, strategies or opinions of SGS.

Widget TwitterFeed not found.

The file '/widgets/TwitterFeed/widget.ascx' does not exist.X

Recent Comments

None

Month List