Click here to Skip to main content
12,996,367 members (66,256 online)
Click here to Skip to main content
Add your own
alternative version


20 bookmarked
Posted 18 Jul 2011

RoutingService on Azure

, 18 Jul 2011
Rate this:
Please Sign up or sign in to vote.
This article describes a design, implementation and usage of the WCF4 Routing Service hosted by Windows Azure.





  • Manageable Routing Service
  • Mapping physical to logical endpoints
  • Managing routing messages from Azure Storage
  • Message Mediation based on the xaml activity stored in the Azure Storage
  • No Service interruptions
  • Adding more outbound endpoints on the fly
  • Changing routing rules on the fly
  • Hosted on the Windows Azure Platform
  • Using 3rd party Azure Storage Explorer, etc.
  • .Net 4 Technologies



In my previous articles such as Routing Manager for WCF4, WorkflowChannel for Routing Service and Enterprise Variables for WF4, I described in details the concept, design and usage of the System.ServiceModel.Routing.RoutingService component hosted on the IIS/WAS on-promises servers. In those articles, the

has been introduced in the business process decomposition and runtime composition based on the metadata stored in the Runtime Repository during the design and deploy time. The RoutingService has a unique feature that allows to update configuration on the fly and reconfiguring its logical mapping and rules between inbound and outbound endpoints.

This article focuses on the hosting a RoutingService on the Windows Azure. I will demonstrate how we can extended its usage for enterprise application driven by metadata stored in the Azure Blob Storage. You will see a message mediation using a workflow activities stored in the Blob Storage, broadcasting mechanism across the web role instances, etc. All these features are enabled by great Microsoft Technologies such as WCF4, WF4 and Windows Azure.

A routing service enables an application to physically decouple a process into the business oriented services and then logical connected (tight) in the centralized repository (Blob Storage) using a declaratively programming. From the architecture point of view, we can consider a routing service as a virtual integration point (hub) for private and public communications.

The following picture shows this architecture:



As you can see, there is a web role of the RoutingService for mapping external endpoints to logical endpoints of the hosted services. In other word, the Router represents a virtual consumer and source of the hosted services and based on the metadata stored in the Blob Storage, it can be mapped to the real physical endpoints. This architecture enables us to centralize physical endpoints in the storage, make agile configuration of the hosted services, logical connectivity to the well-known router endpoints, minimize glitches for reconfiguration connectivity for any reason such as new service version, backup, etc. Of course, the Routers can be connected to the Windows Azure Service Bus for logical connectivity across enterprise and cloud applications.

The major key of the RoutingService is its capability to change a routing table and rules during the runtime. It's natural way, when a routing service is hosted in the web role to use an Azure Storage Service for storing the routing metadata in the blob container. The following picture shows this scenario:


As the above picture shows, the business model represented by Composite Application is virtualized by RoutingService.  The virtualization of the connectivity allows encapsulating a composite application from the physical connectivity. The Application is publicly available via the Router with well-known entry endpoint. Through this endpoint (untyped contract message), we can consume any application service based on the router rules. The business process represented by Composited Application is a container of small business oriented services (workers) which can be connected logically via the Router.

The WCF4 RoutingService extensibility enables to build a remote routing configuration. In the above picture, the Routing Manager represents this extension using a custom service behavior. In this article you will find mechanism how to obtain a routing configuration from the Blob Storage, deserializing this config section and updating a runtime router table. Also, you will find a broadcasting mechanism across multiple web role instances.

Ok, that's the first and major capability of the RoutingService such as connectivity virtualization. We can map, re-route, forward,  back-up, etc. inbound messages based on the router rules and/or a message contents. That's great, thanks to Microsoft WCF Technology.

But, what about message mediation in the pre and/or post processing. Can inbound message be mediated such as transform its body, header, etc. and then forwarded to the outbound endpoint? Can it be done dynamically during the runtime based on the administration changes?

Well, let's step into this feature. As we know, the

must declare all inbound endpoints during the design time. On the other side, the outbound endpoints can be declared on the runtime. From the messaging process point of the view, the outbound message can be fully virtualized and customized. The outbound endpoint represents a target endpoint for forwarded inbound message and the service is its hosting environment. This is a layer of the channel factory. Using a standard way for message mediation, we need to design and deploy this service and public its endpoint. This endpoint will be a target of the forwarded message via Router.

Another more advanced way is to dynamically invoking a mediator from the Runtime Repository. This article will show you how can be a message mediator designed declaratively and stored in the Blob Storage and then invoked by WCF Custom Channel for its runtime projecting.  

The following picture shows this scenario:



You can see, basically, there are two parts to this scenario. The first one is a Blob Storage that is a container of the Workflow Activities and the second one is a Workflow Channel. This WCF custom channel is implemented only for channel factory (no listener) with two message exchange patterns such as Request/Reply and Output. This custom channel is addressed in the RESTful way, for instance: is an example how to invoke a xaml workflow activity Echo from the Storage Container.  Note, the inbound message is passed to the Echo and returned back to the caller. The above Admin Tool shows this activity for message loopback.

That's great, Microsoft WCF4 + WF4 + Azure Technologies enables us in the declaratively way mediate a message on the runtime based on the business needs. This feature allows us to encapsulate and decouple a business model into two parts such as deployment and runtime with capability to administrate on the fly.

One more thing. Using a Windows Azure Storage for storing runtime resources in the Blob container simplifies and standardizes our architecture. The built-in Storage Service allows a very easy access to blob resource. Also, there are 3rd party Azure Storage explorer tools on the market such as Azure Diagnostics Manager, Azure Storage Explorer. etc. for managing contents of the Storage. In this article, I am using an Azure Storage Explorer which allows me to associate a custom editor for the specific blob resource such as Routing Editor and Workflow Designer - see the following picture:

OK, let's get started with Concept and Design of the Manageable Routing Service on the Windows Azure.

I am assuming you have some working experience or understand features of the WCF4 Routing Service and Windows Azure. In addition, I do recommend my articles that were mentioned above.


Concept and Design

Basically, the Concept and Design of the RoutingService hosted on the Windows Azure is similar to having an on-premises server, see in details my article Routing Manager for WCF4. The service behavior is extended for RoutingManager custom behavior for handling routing configuration remotely. The following code snippet shows this configuration feature:

As you can see, the above mandatory routing behavior for RoutingService is empty without any parameters. This is necessary entry requirement for setup a fundamental behavior of the

for empty routing table. The actual content of the router table is done in the custom service behavior such as routingManager. That's why you can see all the properties needed in the standard behavior routing. The additional properties in the routingManager are related for its extension for managing routing configuration remotely.

During the service start-up, the routing table can be configured locally from config file or remotely from resource stored in the Repository (Blob, Database, etc.). This choice is controlled by property enableLoader.

The last action in the routingManager behavior is storing a routing info in the data slot for its later usage of refreshing a routing table. The following code snippet shows this fragment:

serviceHostBase.Opened += delegate(object sender, EventArgs e)
    // make visible for any component in the web role
    ServiceHostBase host = sender as ServiceHostBase;
    RoutingExtension re = host.Extensions.Find<RoutingExtension>();
    if (configuration != null && re != null)

        lock (AppDomain.CurrentDomain.FriendlyName)
                new RoutingManagerInfo 
                    RoutingExtension = re, 
                    RoutingManagerBehavior = this 

Routing Manager Contract

Routing Manager Contract allows to communicate with RoutingService out of the web role. The Contract Operations are designed for pseudo broadcasting and point to point patterns. The following code snippet shows the


[ServiceContract(Namespace = "urn:rkiss/2011/07/ms/core/routing")]
public interface IRoutingManager
    [OperationContract(IsOneWay = true)]
    void Broadcaster(string applicationFullName, Guid ticketId);

    [OperationContract(IsOneWay = true)]
    void SendStatus(string applicationFullName, Guid ticketId);

    [OperationContract(IsOneWay = true)]
    void Refresh(Guid ticketId);

    [OperationContract(IsOneWay = true)]
    void Status(Guid ticketId);

The two operation contracts such as Broadcaster and SendStatus represent out of web role events basically generated by Admin Tool to perform an action in the

across multiple web role instances. As we know, the Azure doesn't support multicasting or broadcasting transport in the current version, therefore we need to simulate this process using a http transport in the point-to-point connectivity. The process of refreshing router table across multiple web roles is very straightforward and based on the following scenario.

The Broadcaster message is sent to the cloud, the routing agent located in the custom channel will collect internal endpoint from all the web role instances and then the dispatcher will dispatch a message Refresh to the web role instance one by one. The process of refreshing routing table across all web role instances is shown in the following picture:


As you can see, the external inbound message is passed through the hardware load balancer to the IIS/WebRole instance. This is a specific inbound endpoint called as /Agent with a routing rules for forwarding a message to the custom channel. This custom channel has only channel factory for processing a message based on the action header. To obtain a collection of the internal endpoint from all web role instances is very straightforward like it is shown in the following code line:

var endpoints = RoleEnvironment.CurrentRoleInstance.Role.Instances
	.Select(instance => instance.InstanceEndpoints["RouterManagerEndpoint"]);

where RouterManagerEndpoint is an internal endpoint of the web role - see the following service definition file:

The above definition file also declares a configuration setting names for accessing a Runtime Repository. Note, that the Endpoint1 is a public endpoint visible to hardware load balancer. The Endpoint2 is an internal endpoint visible only within the compute application.

Message mediation

The concept of the message mediation by Router is based on forwarding an inbound message to the custom outbound channel. This custom channel will invoke an xaml activity from the Repository (Blob Storage) and run its projecting within the service web role. The key component in this design is a WCF custom channel for outbound endpoint. More details about the concept and design of this custom channel can be found in my article WorkflowChannel for Routing Service. This article has similar implementation, focusing on Repository such as Azure Storage Blob.


The above picture shows a concept of the message mediation by Router. The inbound message can be a direct forward without any mediation to the outbound endpoint. That's a standard feature of the RoutingService. Adding a WCF Custom Channel for invoking a Workflow Activity, the message can be forwarded to this custom channel for its mediation. The above picture shows an OneWay message, where Send Activity is sending a mediated message back to the Router for its forwarding to the outbound endpoint. The beauty of this architecture is that all components are loosely decoupled and they can be declared by metadata stored in the Repository. In other words, we can at any time administrate a message mediation and store in the Repository (Azure Storage). For example, contract versioning, client specific contract, etc. Note, that the new message can be sent back to the same Routing Web Role via internal endpoint (no charge for this connectivity) or using an external (public) endpoint via a hardware load balancer. In the case of Request/Reply message exchange pattern, the concept is basically the same. The mediator will have PreProcessor and PostProcessor mediators and final post-processor message will be returned back to the message caller.

In summary of the Concept and Design, you can see how elegantly we can extend a feature of the RoutingService by using a loosely decouple pattern, just adding a WCF Custom Channel for outbound endpoint. In this article, I am using two custom channels, one is for broadcasting message across multiple web role instances and the second one is for message mediation by Router.

Ok, before the implementation section, let's start the usage and test routing service on the cloud.

Usage and Test

First of all, the following are prerequisites:

  • Visual Studio 2010 Sp1
  • Windows Azure SDK v1.4
  • Downloading package for this article
  • Azure Storage Explorer
  • Windows Azure Platform account
  • Some Knowledge and experience for Windows Azure Platform

     Let's make the first step, please download this article and open it with VS2010SP1. You should see the following solution:

The name of the solution is AzureEnterpriseVariables. Don't be confused with this name. It is for generic purpose when a resource stored in the Azure Storage Blob container is called as an enterprise variable. Basically, there are three major solution folders in this solution. The first one is

folder for test purposes only, the second one is an Azure template for Web/Worker Roles including files such as service configuration and definition, and the last folder is actually a web role project for routing manager using a RoutingService.

OK, as the next step, please try to compile this solution. It should be without any errors. As the following picture shows, the configuration setting by default is done for the Emulator. I am going to demonstrate usage of the RoutingService on Azure Emulator and then we can deploy it tothe cloud.

<ServiceConfiguration serviceName="AzureEnterpriseVariables" 

     xmlns="<a href=""></a>" 
     osFamily="1" osVersion="*">
  <Role name="RouterManagerWebRole">
    <Instances count="2" />
      <!-- development -->
      <Setting name="UseDevelopmentStorage" value="UseDevelopmentStorage=true" />
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
      <Setting name="RKiss.EnterpriseVariables.Storage.ConnectionString" value="UseDevelopmentStorage=true" />
      <!-- azure -->
      <!--<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
               value="DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY" />
      <Setting name="RKiss.EnterpriseVariables.Storage.ConnectionString" 
               value="DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY" />-->
      <!-- options -->
      <Setting name="RKiss.EnterpriseVariables.RuntimeRepository.ConnectionString" value="tbd" />
      <Setting name="RKiss.EnterpriseVariables.Endpoint" value="tbd" />

Azure Emulator

Before Ctrl+F5 on AzureEnterpriseVariable project we need to process some manual work on the Development Storage. Run Compute Emulator  and click on Start Storage Emulator on its taskbar icon. Now we can launch Azure Storage Explorer and open DevStorage. For the first time, we need to create new BLOB containers such as activities and routing - see the above solution package. Once we have containers in the Storage, we can store our resources from the solution package.

The following picture shows an activities container (ContentType = application/xaml+xm):

and the following picture show a routing container (ContentType = application/xml):

In the case of some issue with uploading a blob resource, please double click on the row to open a Blob Detail dialog and use Text tab to drop the resource content. This dialog can also be used for change a blob properties such as Content Type, etc.


 Azure Storage Explorer  is a very useful tool, it is free and open source, and therefore we can modify it for our purposes. The current version of this Explorer doesn't have the features what I highlighted in the above Blob Detail picture. This feature allows associating any program based on the blob content type.

There are two very useful editors in the Test/Editors folder. The first one is RoutingEditor to create a routing configuration: 

and the second editor, Worfklow Designer to create xaml activity:


To extended feature of the Blob Details dialog for these two links such as ViewWith... and Edit, please download the source code from Azure Storage Explorer and replace files included in this solution package in the

folder. Btw., I will ask my friend David Pallmann to update this tool for these features in the upcoming month.

OK, at this point, the Storage Emulator should be ready for our test, in other words, the Storage Emulator contains blob containers with resources for activities and routing configuration.

Test 1 - Message mediator

Back to the solution package. Now we need to start a Compute Emulator. You can do it very easily, just Run project AzureEnterpriseVariables. Once we have active emulator (our RouterManagerWebRole is hosted), we can step into our test.

In the Test folder you will see Simulator project. This WindowsForm project has been created for sending any message to the endpoint in the Request/Reply manner or as OneWay.

Launch this Simulator program and select address Press button Invoke to send this message to the Router. You should have the following response on your Simulator:

The message has been sent to the router.svc/Workflow endpoint and based on the router table rules, the message is forwarded to the workflow custom channel for fetching of the xaml activity Echo from the Blob Storage. The custom channel will invoke this activity by bypassing the message back to the caller such as Simulator, so that's why we can see the same message in the response panel.

That' great, you can make another test for selecting In this test, the xaml activity will generate message Hello Cloud

If you succeeded, try to modify or create your own activity using a Workflow Designer in the Storage Container. 

Test 2 - Routing Configuration

As I mentioned earlier, the RoutingService needs to declare all service endpoints for inbound router messages. This collection of service endpoints cannot be reconfigured during the runtime. The following picture shows a service configuration stored as metadata in the Azure Blob Storage:

As you can see, there are 4 endpoints in the RoutingService. The endpoint endpointWorkflow has been described early in the Test 1 when the message mediation has been demonstrated. That's the entry endpoint for forwarding message to the Workflow Channel (see client endpoint


Another interesting endpoint in the RoutingService is endpointAgent. This endpoint is forwarded to the outbound custom channel RouterAgent. Through this routing, we send a message for web role broadcaster to distribute a Refresh request for each routing table across multiple web role instances.

Finally, I can point to the standard routing message to the out of box service. For this example, the routing configuration has a client endpoint named as WeatherService.  The following picture shows a Routing Table for this sample of the routing configuration:


Note, that the first two connectivity rows such as MatchForAgent and MatchForWorkflow should be included in every Routing Table for internal usage. The third, highlighted row shows a routing for public service Weather ( As you can see, this filter uses a custom action message filter with wildcard capability. In this specific filter the action match expression is */*

Let's test this MatchForWeather routing filter. Launch the Simulator program and right click to Load the request message resource Weather.xml. Select address from the comboBox and press the button Invoke. The following result should be shown up:

Great, now let's make changes in the routing configuration on the fly. Basically, we need to update Storage and then send a broadcaster message to the /Agent router.

Step 1. - adding a new public service to the router, for instance:

in this step we adding a new client endpoint such as ConvertTemperatureService like it is shown in the following picture:

Step 2. adding a new custom filter to the Router Table - 


Step 3. - send broadcaster message to refresh a runtime router table

The broadcaster request message can be sent by Simulator. Please, load the Broadcaster.xml file to the request panel, then select an Address from the comboBox and press the button OneWay:

Step 4. - Perform a test

Now, the Router Table in the web role instances should be updated. We can make a test, again using a Simulator with ConvertTemperature.xml resource.

That's all for the Development Storage. You should be familiar to add any service to the Router and send message mediation based on the xaml activity using a development repository such as Compute and Storage Emulators.

OK, Let's move on to the cloud. 

Deploying to Windows Azure

Step 1. - The first step is to create a Storage Blob Containers such as activities and routing. We can use the Azure Storage Explorer tool for this operation. Please, click on the button Add Account to add your Azure account Name and Key. After that, use the same steps like we made for DevStorage (create container, blob, etc.). At this time, you can use a copy blob resource from the DevStorage to the Azure Storage.

Step 2. - make changes in the ServiceConfiguration.cscfg file such as switching section from the development to the

settings and replace YOUR_ACCOUNT_NAME and YOUR_ACCOUNT_KEY with your actually values. The following code snippet shows this file:  

<ServiceConfiguration serviceName="AzureEnterpriseVariables" 

     xmlns="<a href=""></a>" 
     osFamily="1" osVersion="*">
  <Role name="RouterManagerWebRole">
    <Instances count="2" />
      <Setting name="UseDevelopmentStorage" value="UseDevelopmentStorage=true" />
      <!-- development -->
      <!--<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
      <Setting name="RKiss.EnterpriseVariables.Storage.ConnectionString" value="UseDevelopmentStorage=true" />-->
      <!-- azure -->
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 

               value="DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY" />
      <Setting name="RKiss.EnterpriseVariables.Storage.ConnectionString" 

               value="DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNT_NAME;AccountKey=YOUR_ACCOUNT_KEY" />
      <!-- options -->
      <Setting name="RKiss.EnterpriseVariables.RuntimeRepository.ConnectionString" value="tbd" />
      <Setting name="RKiss.EnterpriseVariables.Endpoint" value="tbd" />

Step 3. - Recompile a solution and click on Publish

project. The following dialog will prompt you for deployment project on Azure.


Clicking on button OK, the Windows Azure Activity Log view panel is shown on VisualStudio2010SP1, see the following picture:

Note, that using other option such as Create Service Package Only, the VS will create a package for manual deployment, for instance by Azure portal.

Step 4. Testing

Basically, all tests described in the above section can be repeated by replacing an emulator address by actually azure address




In this section, I will describe some interesting code fragments related to the Azure. The following is a code snippet to obtain a resource from the Azure Blob Storage or Azure Storage Emulator:

public static string GetResource(ResourceType resourceType, string settingName, string container, string name)
  string connectionString = RoleEnvironment.GetConfigurationSettingValue(settingName);
  if (settingName == StorageSettingName || settingName == "UseDevelopmentStorage")
    #region Azure Storage
    var account = CloudStorageAccount.Parse(connectionString);
    var blobClient = account.CreateCloudBlobClient();
    // container
    var blobContainer = blobClient.GetContainerReference(container);
    if (blobContainer == null)
        throw new Exception("...");

    var cloudBlob = blobContainer.GetBlobReference(name);
    if (cloudBlob == null)
        throw new Exception("...");

    config = cloudBlob.DownloadText();
  // ...

The first step is getting a configuration setting value from the RoleEnvironment. Based on this connectionString we can get an Azure account. Once we have an Azure account, we can retrieve a container and specific blob resource. As you can see, it's a straightforward implementation with minimum coding. Thanks for Microsoft.WindowsAzure namespaces.


The next interesting implementation is a broadcaster message for refreshing a runtime routing table across multiple web role instances. The message received by router Agent is forwarded to the outbound custom channel. The following code snippet is its part for broadcasting message:

if (action == RoutingManagerDefaults.ActionBroadcaster)
  #region Broadcaster
  string applicationFullName = body.Element(RoutingManagerDefaults.ApplicationFullName).Value;

  var endpoints = RoleEnvironment.CurrentRoleInstance.Role.Instances.Select(instance => instance.InstanceEndpoints["RouterManagerEndpoint"]);
  foreach (var endpoint in endpoints)
    ChannelFactory<iroutingmanager> factory = null;
      EndpointAddress address = new EndpointAddress(String.Format("http://{0}/Router.svc/Agent", endpoint.IPEndpoint));
      Binding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
      factory = new ChannelFactory<iroutingmanager>(binding);
      IRoutingManager channel = factory.CreateChannel(address);
    catch (CommunicationException)
      if (factory != null && factory.State == CommunicationState.Faulted)
      factory = null;

    catch (Exception)
      if (factory != null && factory.State != CommunicationState.Closed)
      factory = null;

First of all, we need to obtain a collection of internal endpoint from all web role instances. After that, we can create a proxy with specific endpoint address and fire a message Refresh. Note, that this message is sent directly to the IIS VM instance, therefore there is no extra charge for this connectivity and transport.


One more implementation example. To simplify routing messages based on the message action, I created the custom action message filter using a wildcard expression. The following code snippet shows its implementation:

public class ActionMessageFilter : MessageFilter
  public string WildcardExpression { get; set; }

  public ActionMessageFilter(string wildcardExpression)
    this.WildcardExpression = wildcardExpression;

  public override bool Match(System.ServiceModel.Channels.Message message)
    string pattern = "^" + Regex.Escape(this.WildcardExpression).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";
    bool isMatch = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(message.Headers.Action);
    return isMatch;

  public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
    throw new NotImplementedException();



In conclusion, this article described a manageable Router based on the WCF4 Routing Service hosted on Windows Azure. Manageable Router allows to dynamically change routing rules from the centralized logical model stored in the Repository - Azure Blob Storage. The Router represents a virtualization component for mapping logical endpoints to the physical ones and it is a fundamental component in the model driven distributed architecture. This article also shown how message is mediated using the WF4 Activity xaml resource, stored in the Azure Blob Storage. All changes made in the Azure Storage can be propagated across multiple web role instances.



[1] Overview of Creating a Hosted Service for Windows Azure

[2] Creating a Hosted Service for Windows Azure

[3] Windows Azure

[4] WorkflowChannel for Routing Service

[5] Routing Manager for WCF4

[6] Enterprise Variables for WF4



This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Roman Kiss
Software Developer (Senior)
United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
Kanasz Robert28-Sep-12 5:40
mvpKanasz Robert28-Sep-12 5:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170609.2 | Last Updated 18 Jul 2011
Article Copyright 2011 by Roman Kiss
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid