Peter McGrattan’s Weblog

Silverlight, WCF, ASP.NET, AJAX, Graphics, RIA

Silverlight 2 WCF Polling Duplex Support – Part 2: The Server

Posted by petermcg on September 3, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

Introduction

This is the second in a series of posts on the support for duplex communication with a WCF service added to Silverlight 2 Beta 2.  I should point out that this technology in its current form has been deemed by Microsoft as not yet fit for production applications and is therefore currently for evaluation only.  In this post I introduce the server part of a sample application available for download (the download includes the client also) that uses the bi-directional communication discussed in part one.  The sample application as a whole represents a simple client and server application that illustrates polling duplex communication in both directions.  The client displays stock prices in a grid and animates updates received from the server.  The user of the client application can also enter notes against a stock and have those notes propagate to all other connected clients via the server using polling duplex communication in the other direction.  Here is a screenshot of the sample application running with the server and two connected clients, the same stock price updates are being displayed by both clients and the notes entered in Internet Explorer have been pushed to another instance of the client running in Firefox:

Sample Application Running

The Server

The layout of the Visual Studio 2008 solution is as follows:

PollingDuplexDemo Solution Explorer

There are three projects in the PollingDuplexDemo solution, the first is the Silverlight Application project StockClient.  The .xap file produced by building this project is referenced by the Silverlight ASP.NET server control in the StockClientTestPage.aspx file as part of the hosting StockClientHost Web Application project.  The StockServer project is a Console Application that self-hosts the polling duplex WCF service and represents the server.  You can see from the Visual Studio 2008 solution that there are multiple startup projects, the first to start is the StockServer project along with the StockClientHost project shortly after.

Preliminaries

In order to self-host a WCF service in a Console Application the server needs to register itself as the HTTP handler for a certain URI; this is also termed as registering a portion of the local HTTP namespace.  In order to do this the application needs to run as a local Administrator or the current non-administrative user needs to have been assigned to the ACL for a particular URI, you can read more about this here.  If the server Console Application does not have the appropriate permissions a System.ServiceModel.AddressAccessDeniedException exception is raised with the message "HTTP could not register URL http://+:10201/StockService/. Your process does not have access rights to this namespace."

AddressAccessDeniedException Exception

If you are using Visual Studio 2008 as an Administrator or using Windows XP you won’t run into this problem and I have accounted for it on Windows Vista by creating a UAC manifest (see the app.manifest file) that requires the server to be run as an Administrator.  As a result of this configuration you may encounter the following message the first time you press F5:

Restart using different Credentials

Selecting ‘Restart under different credentials’ re-starts Visual Studio as a local Administrator resulting in the server being able to register and listen at the HTTP address it has been configured with.

The WCF Service Interfaces

The StockServer project references the server-side System.ServiceModel.PollingDuplex.dll assembly and defines one WCF service called StockService.  Here are the ServiceContract interfaces defined in the StockServer namespace:

namespace StockServer
{
    [ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(IStockClient))]
    public interface IStockService
    {
        [OperationContract(IsOneWay = true)]
        void Register(Message message);

        [OperationContract(IsOneWay = true)]
        void Sync(Message message);
    }

    [ServiceContract]
    public interface IStockClient
    {
        [OperationContract(IsOneWay = true, AsyncPattern = true)]
        IAsyncResult BeginReceive(Message message, AsyncCallback callback, object state);

        void EndReceive(IAsyncResult result);
    }

    [ServiceContract]
    public interface IPolicyProvider
    {
        [OperationContract, WebGet(UriTemplate = "/ClientAccessPolicy.xml")]
        Stream ProvidePolicy();
    }
}

Three interfaces are defined in all: IStockService, IStockClient and IPolicyProvider.  IStockService defines the two methods Register and Sync specified as One Way operations, hence the method signatures cannot specify anything other than void as the return type.  Setting IsOneWay to true has the effect that clients will not block waiting for a return message after having called the service as they would with a default request response operation.  A client configured for polling duplex will rather poll asynchronously and pick up messages pushed to them from the service as discussed in part one.  You may also recall these two operations from the value of SOAPAction in the traced HTTP requests in the previous article.  The IStockClient type is specified via the CallbackContract property as the type that will callback to the client from instances of services that implement IStockService.

IStockClient defines the Receive operation as an asynchronous operation by setting AsyncPattern to true and defining Begin and End methods matching the required pattern.  This method is specified as asynchronous to cater for a problem I was encountering when one client aborted or faulted while many other clients were connected.  In this scenario a synchronous method call back to the faulted client resulted in the server thread blocking until the PollTimeout period elapsed for that client with the exception shown in the previous post.  This is fine for the faulted client but the blocking stopped the other connected clients receiving their updates.  The asynchronous version handles the reply using the thread pool so that in the same situation the faulted client will block and timeout on a thread pool thread while the other connected clients still receive their updates as normal.

The IPolicyProvider interface sets up a creative solution (thanks to this post) to the problem of serving up policy requirements to Silverlight 2 clients when using a self-hosted WCF service.  The only change I had to make was to include http-request-headers="*" in the response to ensure compatibility with the Silverlight 2 Beta 2 policy requirements instead of Beta 1.  The declaration above specifies that the ProvidePolicy method should be called in the event of a GET request whose URI includes the relative string specified in UriTemplate.  I have not included support for Flash compatible crossdomain.xml files but this could be easily added using the same method.  WebGet was introduced as part of WCF 3.5, its interesting (though not relevant to Silverlight self-hosted policy requirements) to note that the UriTemplate can also contain curly bracket placeholders that resolve to method parameters for use with REST requests, similar functionality is provided in ASP.NET MVC routing.

The WCF Service Class

The following shows how the previously discussed interfaces are used throughout the declaration of the StockService class.  The StockService class implements only two of the interfaces: IStockService and IPolicyProvider, the usage of the IStockClient interface is shown in the Register method:

namespace StockServer
{
    public sealed class StockService : IStockService, IPolicyProvider
    {
        private StockClient client = null;

        public void Register(Message message)
        {
            client = new StockClient(OperationContext.Current.GetCallbackChannel<IStockClient>(),
                                          "Silverlight/IStockService/Receive");
            client.Faulted += OnClientFaulted;

            InitialReply();

            StockGenerator.Instance.DeltaReceived += OnDeltaReceived;
        }

        public void Sync(Message message)
        {
            Stock delta = message.GetBody<Stock>();

            StockGenerator.Instance.Sync(delta);
        }

        private void InitialReply()
        {
            foreach (Stock stock in StockGenerator.Instance.DemoStocks)
            {
                Reply(stock);
            }
        }

        private void Reply(Stock stock)
        {
            if (client != null)
            {
                client.Send(stock);
            }
        }

        private void OnDeltaReceived(object sender, DeltaReceivedEventArgs e)
        {
            Reply(e.Stock);
        }

        private void OnClientFaulted(object sender, FaultedEventArgs e)
        {
            if (client != null)
            {
                StockGenerator.Instance.DeltaReceived -= OnDeltaReceived;
                client = null;
            }
        }

        public Stream ProvidePolicy()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(File.ReadAllBytes("ClientAccessPolicy.xml"), false);
        }
    }
}

The StockService does not specify a ServiceBehavior attribute as part of it’s declaration so the defaults apply, the most relevant being InstanceContext.PerSession and ConcurrenyMode.Single.  As such, a new instance of the StockService WCF service is created for every client that connects although it is also possible to configure the service as a singleton using the code download with very few changes if that is something you are interested in.

The Register method is called by clients when they first connect to the service, the Message parameter although required by the underlying framework is not used in the method body.  The body firstly creates a new instance of the StockClient class (see code download) passing two parameters to the constructor: a reference to the channel back to the client along with the SOAPAction to use when sending a return message.  Note this SOAPAction string does not mention the IStockClient interface but rather the IStockService interface and contains the name of the synchronous version of the Receive method which is not defined anywhere in user code.  The rest of the method and indeed the class is fairly self-explanatory and centres around subscribing the current instance of StockService to receive deltas from the singleton StockGenerator class (see code download) when a client connects and unsubscribing when a fault occurs attempting to communicate with that client.

The Sync method does make use of the Message parameter sent from the client to extract the Stock (see code download) object and the notes entered against it.  This is again passed on to the singleton StockGenerator class to update it’s state and raise a notification to all other subscribed clients.  The implementation of the ProvidePolicy method simply returns a stream containing the contents of the ClientAccessPolicy.xml file (which is copied to the output directory of the executable) to satisfy the policy requirements required to allow Silverlight clients to connect to this self-hosted service cross-domain.

Hosting the WCF Service

The hosting information for the StockService service is split between the App.config file…

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <diagnostics performanceCounters="All" />
    <services>
      <service name="StockServer.StockService"
               behaviorConfiguration="StockServiceBehaviour">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:10201/StockService" />
          </baseAddresses>
        </host>
        <endpoint address="mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="StockServiceBehaviour">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

… and the StockServiceHost class that inherits from ServiceHost:

namespace StockServer
{
    public class StockServiceHost : ServiceHost
    {
        public StockServiceHost(object singletonInstance, params Uri[] baseAddresses)
            : base(singletonInstance, baseAddresses)
        {
        }

        public StockServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }

        protected override void InitializeRuntime()
        {
            Uri baseAddress = this.BaseAddresses[0];
            Uri policyAddress = new Uri(baseAddress.AbsoluteUri.Replace(baseAddress.AbsolutePath, ""));

            this.AddServiceEndpoint(
                typeof(IStockService),
                new PollingDuplexHttpBinding(),
                baseAddress);

            this.AddServiceEndpoint(
                typeof(IPolicyProvider),
                new WebHttpBinding(),
                policyAddress).Behaviors.Add(new WebHttpBehavior());

            base.InitializeRuntime();
        }
    }
}

This is where the server instance of PollingDuplexHttpBinding is instantiated and published as a service endpoint on the base address retrieved from the configuration file.  Two other endpoints are defined for this service, one is the Metadata Exchange endpoint which you can navigate to in a browser when the server is running; the other is the endpoint for the policy response bound to the root of the base address.  The constructors included simply defer to the base constructors of ServiceHost, the first one allows this class to be optionally used as a host of a singleton polling duplex service instance.  When the Console Application starts it simply creates an instance of the StockServiceHost class and opens the communication channel to accept clients:

namespace StockServer
{
    class Program
    {
        static void Main(string[] args)
        {
            // Set Console display info
            Console.Title = "Polling Duplex Stock Service";
            Console.ForegroundColor = ConsoleColor.Cyan;

            using (StockServiceHost serviceHost = new StockServiceHost(typeof(StockService)))
            {
                serviceHost.Open();

                Console.WriteLine("Polling Duplex Stock Service Started...");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.ReadLine();
            }
        }
    }
}

The same stock prices are sent to all client channels at approximately the same time in each StockService instance’s handler for the singleton StockGenerator class’s DeltaReceived event via the StockClient instance.  The full code for the definitions of the StockGenerator and StockClient classes is available in the code download.  Currently new deltas are generated every 250.0 milliseconds using the ThreadPool, obviously the more this interval is reduced the more noticeable the performance degradation; especially below 100.0 milliseconds on my machine with two clients in separate browsers or tabs things eventually get out of sync.  Comparing this solution to a similar solution I’ve published using Sockets which can keep clients in sync at 1.0 millisecond intervals it’s clear to see there is work to be done on performance and it’s good to see the Silverlight Webservices Team acknowledge this themselves.

About these ads

18 Responses to “Silverlight 2 WCF Polling Duplex Support – Part 2: The Server”

  1. [...] generating these transmissions, download the sample Visual Studio 2008 solution attached to parts two or [...]

  2. [...] all 3 parts were posted the same day :) … This first one is on the Architecture, then follows Silverlight 2 WCF Polling Duplex Support – Part 2: The Server , and finally Silverlight 2 WCF Polling Duplex Support – Part 3: The Client — all really good [...]

  3. Hello Peter,

    Thanks for the great tutorial about WCF Polling Duplex.

    I’m now experimenting in building a chat application based on Polling Duplex. I have the client and the server already up and running, but I’m facing a problem with the InactivityTimeout. After InactivityTimeout occur, the communication object I use goes into a Faulted state and it can’t be used anymore. I’m stuck in this situation and I can’t understand what should I do to use the communication object again? Should I open the channel again or what? I’ll be happy if you can help me with this. Thank you.

  4. Peter McGrattan said

    Hi Emil,

    When the InactivityTimeout occurs you need to catch the resulting TimeoutException exception, once the communication object is in a faulted state you need to close it. Once the channel is closed you should start the lifecycle over again from CreateChannel.

    To prevent the InactivityTimeout exception from occurring in the first place you could implement some sort of basic keep-alive message that is sent within the 10 minutes to let both parties know the other is not inactive. This would need to be sent on the same channel you wish to keep open.

    Hope this helps,

    Peter

  5. Joel said

    Instead of instantiating the PollingDuplex endpoint in code like this:

    this.AddServiceEndpoint(
    typeof(IStockService),
    new PollingDuplexHttpBinding(),
    baseAddress);

    Would it be possible to specify the endpoint in App.config? Is it possible to access the System.ServiceModel.PollingDuplex from App.config?

  6. Peter McGrattan said

    Hi Joel,

    Great question – it is possible to specify the endpoint(s) in App.config.

    See this post for the steps involved.

    Hope this helps,

    Peter

  7. Scott Edmondson said

    Hi Peter,

    Excellent series of posts! The detail is really appreciated. Regarding making the service a singleton: what is required other than specifying the appropriate ServiceBehavior attribute?

    I’m hosting my service in IIS, and I’m using the @Service need to use the @ServiceHost directive in my .svc file. I’ve created a “factory” wrapper class that extends ServiceHostFactoryBase; is that correct? Is all that consistent with the desired singleton behaviour?

    Cheers,
    Scott

  8. Peter McGrattan said

    Hi Scott,

    In general, for simple scenarios you should just derive from ServiceHostFactory and override CreateServiceHost, for more advanced customization use ServiceHostFactoryBase.

    For singleton behaviour mark your service itself with InstanceContextMode = InstanceContextMode.Single via the ServiceBehaviour attribute.

    Hope this helps,

    Peter

  9. Armando said

    Hi Peter,
    Great post,

    We are using this code as our basic architecture for a biz application. We changed a little bit the handler on the silverlight client, as we needed to catch any possible exception on the client.
    Currently we are facing two issues, one is we haven’t been able to host the duplex service in a window’s service.
    The other is the console application closes after a couple of hours.
    Wondering if you could help us. I’ll be happy to provide our code.

  10. Tim said

    Hi Peter, thanks for the excellent post. It has been a huge help.

    I’ve run into one problem with the code. If I close down the ServiceHost and then recreate it, I get an exception saying a registration already exists for the specified URI.

    If I do:
    host.Close();
    then at a later point:
    host = new StockServiceHost(...);
    host.Open();

    then I get this exception:

    System.InvalidOperationException: The ChannelDispatcher at ‘http://localhost:8732/AlarmService’ with contract(s) ‘”IAlarmService”‘ is unable to open its IChannelListener. —> System.InvalidOperationException: A registration already exists for URI ‘http://localhost:8732/AlarmService’.

    Do you know how to unregister the endpoints when the service is closed?
    Thanks!

  11. Tim said

    PS
    The “AlarmService” in the exception posted above should be “StockService”. I forgot to rename it back to the context of this article.

  12. Leon Shahar said

    Hi Peter,
    I’m following your post and the download code as a guide to implement a self-hosted wcf service and a Silverlight 3 client.
    My service is a very simple one declaring a single “Login” operation in its contract (which follows exactly the same signatures as the download code).
    I’ve put together all the pieces, the only exception is that I created a service host console application and added a reference to the service assembly.
    When I run the application, first I launch the service host I get an Unhandled exception Sys.InvalidOperationException.ManagedRuntimeError error # 404… Server Not Found.
    I’ve also built the solution in the download code, converted it to vs2008 and changed to MessageVersion.Soap12WSAddressing10. When I run the sample I get the same error.
    I’m stuck with this issue, can you suggest a fix?
    I’ll appriciate any help here

    Regards
    Leon

  13. Mazhar Karimi said

    It would be better to host the service on IIS, many users would not be able to access it using custom port.

    when you are hosting the service using StockServiceHost, specify the htttransport and other configs, it would help alot to perform well but to some extent (better to deploy on IIS.)

    As in your service you don’t have any global Data objects to manage, make the concurrency mode multiple, this will improve alot, and if you do have global data objects, lock them before them changing.

    Deployment on IIS will ease the Client Implementation part as well.

  14. Leon Shahar said

    Thanks Mazhar for your comments.
    It’s true, hosting your services in IIS is far easier, but is quite limiting.
    In a distributed topology services may run on boxes without IIS.
    I found, eventually, a solution for that issue which involves programmatic configuration on both client and server. I whish VS2008 and WCF would provide better tools to configure this scenario.

    Regards
    Leon

  15. [...] “Silverlight 2 WCF Polling Duplex Support” articles: Part 1: Architecture, Part 2: The Server and Part 3: The Client. For an interface that boils down to 3 methods: Subscribe, Publish and [...]

  16. Brent said

    great post….

    really appreciate it!!!

  17. [...] Silverlight 2 WCF Polling Duplex Support – Part 2: The Server [...]

  18. [...] http://petermcg.wordpress.com/2008/09/03/silverlight-polling-duplex-part-2-the-server/ [...]

Sorry, the comment form is closed at this time.

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: