Peter McGrattan’s Weblog

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

Posts Tagged ‘Self Hosted’

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.

Posted in Silverlight, WCF | Tagged: , , , , , | 18 Comments »

 
Follow

Get every new post delivered to your Inbox.