Saturday, 21 August 2010

Chubby RESTful Clients using Silverlight and WCF

wcfsilverlight  [Concrete/Interesting] Many years ago I was working on a project. We needed a application that worked in a browser but everything came from the local machine. Later I started to notice devices like Routers that had a web interface…just connect using HTTP to the device IP address and there you are.

I used a similar technique in my Ghostfiles Service application (http://www.lowrieweb.com/ghostfiles.htm).

Chubby Client Architecture

Ghostfiles is a Windows Service Application that runs as a lightweight HTTP server on a private HTTP port. If you point your browser at it, the service returns back application pages. It’s just like having Apache or IIS running, only lightweight with only the pages of the application supported. This is what I called a Chubby Client, a thin client interface delivered to a browser by a local web and application server.

The basic architecture used in Ghostfiles is shown here:

image 

Page requests (GET/PUT) from the browser are handled by the light HTTP Web Server. This directs the calls to Page Service.

Page Services are responsible for mapping the request against a template page. The template defines the basic HTML resources associated with this request. The template can contain standard mark-up as well as JavaScript.

So far so good. The template may also contain meta-tags that are interpreted by the meta-tag handler. This sub-system is responsible for mapping the tags to calls down to the business services. So for example, a table on the page is defined by a meta-tag, <APP:DoSomething></APP:DoSomething>. This maps to a business function DoSomething();

Looks familiar – well it’s very similar to PHP or old style ASP in how it works. And it works well from an application and UI point of view. However, the code is hard to maintain, adding new user interfaces is an effort and the implementation is limited to HTML and JavaScript, so not an ideal platform going forward.

Chubby Client Grows Up

.Net provides us with a way to take the Chubby Client into the realm of Rich Internet Applications (RIA). To achieve this we need to look at two core technologies, Silverlight Out Of Browser Clients and Self Hosted WCF:

image

WCF Self Hosting

Windows Communication Foundation is a well balanced communication framework. One feature of WCF is the range of hosting options. A couple of these allow us to deploy a service implementation without the overhead of IIS. WCF Self Hosting enables us to deploy communication services fully encapsulated as a standalone executable or if preferred, Windows Service Application.

I’m going to focus on RESTful web services for HTTP JSON clients. For the purpose of this blog, I’m going to create a Console based REST service that exposes one simple interface called Colour:

URI: http://localhost:8000/Chubby/Colour
MIME: application/json
HTTP Method: GET

The response to this call is a random colour reference in the form of:

{ “Colour” : “#FFRRGGBB” }

The steps to create the service are very easy under Visual Studio 2010:

Step 1

Create a new project based on a C# Console Application:

image Next add references to System.ServiceModel, System.ServiceModel.Web and System.Runtime.Serialization to the console application and add the following statements to the top of your Program.cs file:

using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.IO;

Note: You should check the Target Framework for the project, in Project Properties. This should be set to .NET Framework 4 to ensure the Reference filters are correctly set:

image

Step 2

Define and implement the service contracts. This defines the JSON services that will be exposed by this console application and the implementation behind these services.

Note: As well as the Chubby Service, we also need to define and implement Cross Domain Policies for our service. This is important because we are calling the service from Silverlight and Silverlight will need to be reassured that it can use the service:

#region Service Contract

    [ServiceContract]
    public interface IChubbyServices
    {
        [OperationContract]
        [WebGet(UriTemplate = "Colour", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
        JSONColour GetColour();
    }
    #endregion

    #region Service Contract for Cross Domain Policies

    [ServiceContract]
    public interface IPolicyRetriever
    {
        [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
        Stream GetSilverlightPolicy();
        [OperationContract, WebGet(UriTemplate = "/crossdomain.xml")]
        Stream GetFlashPolicy();
    }
    #endregion
    
    #region Data Contracts
    
    [DataContract]
    public class JSONColour
    {
        [DataMember]
        public string colour = String.Empty;
    }
    #endregion

    #region Service Implementation

    public class ChubbyServices : IChubbyServices, IPolicyRetriever
    {
        public JSONColour GetColour()
        {
            //Return a random colour from the list below
            string[] colours = { "#FFFF0000", "#FF00FF00", "#FF0000FF", "#FFFFFF00", "#FFFF00FF", "#FF00FFFF", "#FF000000" };
            JSONColour colour = new JSONColour();
            Random rand = new Random();
            colour.colour = colours[rand.Next(7)];
            return colour;
        }
        Stream StringToStream(string result)
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(Encoding.UTF8.GetBytes(result));
        }

        public Stream GetSilverlightPolicy()
        {
            string result = @"<?xml version=""1.0"" encoding=""utf-8""?>
            <access-policy>
               <cross-domain-access>
                  <policy>
                     <allow-from http-request-headers=""*"">
                        <domain uri=""*""/>
                     </allow-from>
                     <grant-to>
                        <resource path=""/"" include-subpaths=""true""/>
                     </grant-to>
                  </policy>
               </cross-domain-access>
            </access-policy>";
            return StringToStream(result);
        }

        public Stream GetFlashPolicy()
        {
            string result = @"<?xml version=""1.0""?>
            <!DOCTYPE cross-domain-policy SYSTEM ""http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"">
            <cross-domain-policy>
               <allow-access-from domain=""*"" />
            </cross-domain-policy>";
            return StringToStream(result);
        }
    }
    #endregion

Quick note about the class JSONColour. The service is designed to respond with Mime type “application/json”. To do this we need to instruct WCF to serialize the data in JSON format. This is defined by the Operation Contract against the method, specifically:

ResponseFormat = WebMessageFormat.Json

Now to ensure there is something for WCF to serialize, we create a Data Contract for serialization and define a structure to be serialized. In our case we call the structure JSONColour. With WFC that is all that is required to ensure correct serialization for JSON.

Step 3

Now we need to implement the host responsible for running the service:

#region Host Implementation

    class Program
    {
        static void Main(string[] args)
        {
            string baseAddress = "http://localhost:8000";

            using (ServiceHost host = new ServiceHost(typeof(ChubbyServices), new Uri(baseAddress)))
            {
                host.AddServiceEndpoint(typeof(IPolicyRetriever), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
                host.AddServiceEndpoint(typeof(IChubbyServices), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior("Chubby"));
                ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
                smb.HttpGetEnabled = true;
                host.Description.Behaviors.Add(smb);

                host.Open();

                Console.WriteLine("WCF Host Running...");
                Console.WriteLine();

                foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
                    Console.WriteLine("Supported Endpoint for {0} at address {1}", endpoint.Binding.GetType().Name, endpoint.Address.Uri);

                Console.WriteLine();
                Console.WriteLine("Press  to shutdown");
                Console.ReadLine();

                host.Close();
            }
        }
    }
    #endregion

Here we are creating a host and setting up the appropriate WebHTTPBindings as required by REST.

Step 4 

Now all we need to do is compile, run and test. When run the service should open a console window similar to that shown below:

image The console should show the application started and the host running.

Since this is a REST host, we should be able to test the service from a browser. Open your browser and go to the URL shown below:

http://localhost:8000/Chubby/Colour

The browser should respond with the JSON response data. Typically the browser will not recognise the Mime type and will allow you to save the response in a file:

image If you save the response to a file then open the file it should look like this:

image

Excellent, just what we were looking for.

Silverlight Out of Browser

The service is half the story. To complete our Chubby Client we need a client that calls on our service. For this we are going to use Silverlight and implement and Out of Browser client.

Our client will be simple, with a single showing a TextBlock and a Button. The code behind will implement the call to the web service and handle the response. Let’s create the client:

Step 1

Create the Silverlight Client App. We are going to choose the Silverlight Application Template:

image

We need a web page to host our apps. Let’s choose an ASP.Net Web Site:

image 

Open MainPage.xaml and create our UI. All we need for this demo is a TextBlock to show the colour returned from our service call and a Button to kick things off:

image

<usercontrol xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:class="ChubbyClient.MainPage" mc:ignorable="d" d:designheight="300" d:designwidth="400">

   <grid x:name="LayoutRoot" background="White">
        <textblock height="68" horizontalalignment="Left" margin="65,74,0,0" name="textBlock1" text="TextBlock" verticalalignment="Top" width="264" fontfamily="Comic Sans MS" fontsize="32" fontweight="Bold" textalignment="Center"></textblock>
        <button name="button1" height="55" horizontalalignment="Left" margin="145,187,0,0" verticalalignment="Top" width="104" fontsize="18" content="Press Me">
    </grid>
</usercontrol>
Step 2

Now we can wire up the button. Double click on the button in the IDE to create an event handler in the code behind. In this event handler we are going to call the web service.

I’m going to use a WebClient() object to make an HTTP GET call to the web service. The data response will be given to us in an asynchronous handler:

private void button1_Click(object sender, RoutedEventArgs e)
{
   WebClient client = new WebClient();
   client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
   client.DownloadStringAsync(new Uri("http://localhost:8000/Chubby/Colour"));
}

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
   if (e.Error != null)
      return;
}

The results are found in e.Results in JSON encoding. All we need to do now is lightly parse the results, pulling out the colour and apply that colour to our styles. First we have to add a reference for System.Json to the project and a using statement to the code. We will use the System.Json assembly to help us decode the results.

Now to parse the results and update the view:

private Color GetColorFromHexa(string hexaColor)
{
   return Color.FromArgb(
      Convert.ToByte(hexaColor.Substring(1, 2), 16),
      Convert.ToByte(hexaColor.Substring(3, 2), 16),
      Convert.ToByte(hexaColor.Substring(5, 2), 16),
      Convert.ToByte(hexaColor.Substring(7, 2), 16));
   }

   void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
   {
      if (e.Error != null)
         return;

      JsonObject jsonDoc = (JsonObject)JsonObject.Parse(e.Result);
      string colour = jsonDoc["colour"];

      //Update the view
      textBlock1.Foreground = new SolidColorBrush(GetColorFromHexa(colour));
}
Step 3

The last step is to make the Silverlight run Out of Browser. This is very simple. Open up the properties for the Silverlight client and select the Siverlight tab. All we need to do is check the ‘Enable running application out of browser’. You may also want to change the out of browser settings.

image

That’s it, job done.

Chubby Client Grows Up

As you can see, we can build Silverlight out of browser applications that are serviced locally by a WCF console application.

This concept can be taken further in a number of directions. We can package the WCF service as a windows service. We can make an installer that puts both client and services onto the local machine, registering the client to access the local services. We can take the same client and call a remote service – so chubby client for desktop and thin client for RIA.

From a code point of view, I’ve done a quick and dirty here. The client should be better architected to follow a MVC or MVVM patterns. The server should be more modular with the service contracts in separate assemblies. I’m sure there is a lot more that can be improved.

To help you get started, I’ve published the code above to Google Code.

http://code.google.com/p/chubbyclient/

Have fun.

No comments:

Post a Comment