CodeProject
Some months ago I started to develop a special project and I needed the possibility to add multiple WCF services and make them available to through one single endpoint.
Looking for a solution, I stumbled over WCF-Routing services. This kind of service allows you to bundle multiple WCF endpoints from multiple WCF services and expose them through a single endpoint (Service Aggregation).
But that is not the only advantage you have. By intercepting the messages that come in through the WCF-Routing service you could also check for valid user-tokens implement authentication and more.
With all the required information in place, I wrote this little POC (Proof Of Concept) to demonstrate how it could be done :)
Here is the official MSDN definition of what a WCF-Routing service is:
The Routing Service is a generic SOAP intermediary that acts as a message router. The core functionality of the Routing Service is the ability to route messages based on message content, which allows a message to be forwarded to a client endpoint based on a value within the message itself, in either the header or the message body.
Another great advantage of the WCF-Routing services is that routes can be configured dynamically at runtime using RoutingExtensions based on the IExtension interface. This allows you to add endpoints of fellow WCF-Services by using Message filters. Here is the official MSDN definition:
The message filters used by the Routing Service provide common message selection functionality, such as evaluating the name of the endpoint that a message was sent to, the SOAP action, or the address or address prefix that the message was sent to. Filters can also be joined with an AND condition, so that messages will only be routed to an endpoint if the message matches both filters. You can also create custom filters by creating your own implementation of MessageFilter.
Your can read more about the different types of Message-Filters here:
Message Filters on MSDN
In this implementation I use the EndPointAddressMessageFilter to expose the endpoint-address of the WCF- Service on the Routing-Service.
Like mentioned before, WCF-Routing services can be configured dynamically to add WCF-Service endpoints at runtime. This is something I wanted to run fully automated and to scalable on Windows Azure.
I am not going into the details on how WCF-Routing services work. You can read anything about WCF-Routing services on MSDN:
Routing
The basic Architecture
The basic architecture of the solution is not very complicated. The main part is the WCF-Routing Service itself. If a specific WCF-Service wants to be added to the router, it puts a specific message onto the Service Bus and the WCF-Routing-Service receives those messages.
Already added services will be processed from a SQL-Table to be added after the re-start. This means that we don’t have to restart each WCF-Service that was already added to the Service-Routing table.
Worker Role Configuration (WCF-Routing service)
The “Endpoints” tab
We need a public endpoint for our routing service. On the property page for our worker role we can set an public input endpoint. This endpoint will accept simple TCP messages on the predefined port 10100.
The “Settings” tab
To be able to address the service later via a domain name and not only using the IP-Address of the host, an additional entry “Domain” is added. Because the service is running only on the local machine, the value is set to “localhost”. You can change that later, if you want to run this POC on Windows Azure.
The “ServcieDefinition.csdef” file
To allow the worker role to run with elevated privileges and to open custom ports for its endpoints, an “Runtime” element needs to be added within the “WorkerRole” tag:
<Runtime executionContext="elevated" />
The POC implementation
Implementing the basic Routing-Service
We need to register a WCF service-host of type RoutingService in our worker role to fire-up a new WCF-Routing-Service. Personally I prefer to configure each WCF-Service via code and not via XML configuration file.
This is what worker role with a basic WCF-Router implementation looks like (more about the details later):
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.ServiceModel;
using System.ServiceModel.Routing;
using SelfUpdatingServiceRouter.Behaviours;
using System.ServiceModel.Description;
namespace SelfUpdatingServiceRouter
{
public class WorkerRole : RoleEntryPoint
{
private string endPointAddress;
private string listenAddress;
private RoleInstanceEndpoint endpointAzure;
public override void Run()
{
using (ServiceHost host = new ServiceHost(typeof(RoutingService)))
{
this.ConfigureServiceHost(host);
while (true)
{
Thread.Sleep(10000);
Trace.TraceInformation("Routing Service Working...", "Information");
Trace.TraceInformation(listenAddress);
}
}
}
private void ConfigureServiceHost(ServiceHost host)
{
try
{
endpointAzure = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["RoutingServiceMain"];
this.endPointAddress = string.Format("http://{0}/ServiceRouter", endpointAzure.IPEndpoint);
this.listenAddress = string.Format("http://{0}:{1}/ServiceRouter/", RoleEnvironment.GetConfigurationSettingValue("Domain"), endpointAzure.IPEndpoint.Port);
var httpBinding = new BasicHttpBinding();
httpBinding.SendTimeout = TimeSpan.FromMinutes(1);
httpBinding.ReceiveTimeout = TimeSpan.FromMinutes(1);
var routerEndpoint = host.AddServiceEndpoint(typeof(IRequestReplyRouter), httpBinding, this.endPointAddress, new Uri(this.listenAddress));
routerEndpoint.Name = "RouterMain";
host.Description.Behaviors.Add(new RoutingBehavior(new RoutingConfiguration()));
host.Description.Behaviors.Add(new RoutingUpdateBehaviour());
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(this.endPointAddress);
host.Description.Behaviors.Add(smb);
ServiceDebugBehavior debug = host.Description.Behaviors.Find<ServiceDebugBehavior>();
if (debug == null)
{
host.Description.Behaviors.Add(
new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
}
else
{
if (!debug.IncludeExceptionDetailInFaults)
{
debug.IncludeExceptionDetailInFaults = true;
}
}
host.Open();
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
}
public override bool OnStart()
{
ServicePointManager.DefaultConnectionLimit = 12;
return base.OnStart();
}
}
}
The Service Bus part – background
In the first version of this service, I have used the pure Service Bus .NET assemblies. A few days ago I discovered the CloudFx (Cloud Application Framework & Extensions) library on NuGet. This library was originally written by Microsoft folks to boost the development of cloud based projects.
It is based on the “Reactive Extensions” (Rx) library. The CloudFx library allows you to use a publish/subscribe pattern on data-streams. Data streams can be events, Twitter feeds, web service requests and more using LINQ. Here is a small sample, that uses a MouseClick event with the publish/subscribe pattern (Source: MSDN):
public ISubject<MouseEventArgs> MouseMove;
MouseMove.OnNext(args);
MouseMove.Subscribe(args => Display(args));
As you can see there is no usual Event/Delegate pattern visible anymore. An ISubject of type MouseEventArgs is created and the next time a mouse event occurs, the EventArgs are published to all subscribers. A few lines of code to create some awesomeness – love it!
Here are some of the great features:
- Send and receive messages asynchronously using a Service Bus Topic
- Send and receive unicast and multicast messages with Service Bus
- Simple use of “Put” and “Get” (send and receive) methods (Very handy to upload and download blobs)
- Error handling
- Inter-role communication
In general, it greatly reduces the code you need to implement these scenarios. Please see the excellent CloudFx Samples on MSDN.
Basic DAL implementation
The RouteMeModel class
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Models
{
[Table("RouteModel")]
public class RouteMeModel
{
[Key]
[Column("ServiceUID")]
public string SericeUID { get; set; }
[Column("EndPointAddress")]
public string EndPointAddress { get; set; }
[Column("ServiceName")]
public string ServiceName { get; set; }
[Column("ContractName")]
public string ContractName { get; set; }
[Column("FullAssemblyName")]
public string FullAssemblyName { get; set; }
}
}
The RoutMeModel class will transport all the required data over the Service Bus to the Routing-Service, which includes:
- The endpoint address
- The name of the service
- The full name of the contract (including the namespace) implemented by the service
- The file-name of the executing assembly hosting the service implementation
The RouteModel class will be also used as a EF-entity to manage the WCF endpoint data.
Prerequisites
For our local development we will use SQL Server Express 2012. If you don’t have SQL Server Express 2012 installed on your local machine, you can get it here:
Microsoft SQL Server 2012 Express
Adding a DbContext implementation and configure it using Fluent-Configuration
Because we have a very simple model, the configuration of our DbContext:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Models
{
public class RouteContext:DbContext
{
public DbSet<RouteMeModel> Services { get; set; }
public RouteContext()
: base(@"[YOUR SQL CONNECTION STRING HERE")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
this.Configuration.AutoDetectChangesEnabled = true;
modelBuilder.Entity<RouteMeModel>().HasKey<string>(r => r.SericeUID);
modelBuilder.Entity<RouteMeModel>().Property(r => r.ServiceName).HasMaxLength(120);
modelBuilder.Entity<RouteMeModel>().Property(r => r.ContractName).HasMaxLength(511);
modelBuilder.Entity<RouteMeModel>().Property(r => r.EndPointAddress).HasMaxLength(2083);
modelBuilder.Entity<RouteMeModel>().Property(r => r.FullAssemblyName).HasMaxLength(356);
}
}
}
The next thing to do is to generate the data-table using the “Package Manager Console” and EF. First you need to enable migrations, then add a migration and the last thing to do is to update the database. Here are the three commands to type into the “Package Manager Console”:
- enable-migrations
- add-migration Initial (initial is the name for the migration)
- update-database
This will create the data-table using the code-first approach.
Dynamic WCF-Router configuration – base scenario
It must be possible to send WCF-Service endpoint-data to a Service Bus topic subscriber. The subscriber (in our case the WCF-Routing service) manages the received endpoint-data and transforms it to endpoints that need to be added dynamically to the routing table of the WCF-Routing service. This a prefect use-case for an unicast scenario. Messages are only addressed to one specific receiver, which is the WCF-Routing service.
Installing CloudFX using NuGet
We need to install the latest pre-release to make it work with the latest Azure SDK (2.1 at the time of this writing).
Configuring CloudFx
CloudFx is configured using standard app.config files. To simplify the configuration process, I took the configuration file from the CloudFX samples solution, and adapted it accordingly to my needs. The most important setting is the part where the Service Bus settings take place:
<ServiceBusConfiguration defaultEndpoint="wcfrouter" defaultNamespace="[YOUR NAMESPACE HERE]" defaultIssuerName="owner" defaultIssuerSecret="[YOUR KEY HERE]
<add name="wcfrouter" endpointType="Topic" topicPath="WcfRouterSample" />
</ServiceBusConfiguration>
In short the values are:
- defaultEndPoint is the name for the CloudFx endpoint
- defaultNamespace is your Service Bus namespace name
- defaultIssuer is the issuer of your ACS token on Azure (standard owner)
- defaultIssuerSecret is the default key (ACS token) issued by “owner”
- endPointType is the type of the endpoint like topic, queue, relay and so forth
- topicPath this is where you set the name of the topic, that will be created if it does not exist already
The subscription name will be set to “To” property of the routing-message context. In this sample “ServiceRouter”. More about the routing-message context later.
WCF-Router side implementation
On the WCF-Router side the following things need to be done:
- Check, if there are entries in the RouteModel table, if so, check if the services have been added, if not, add the new route to the table
- Listen for incoming requests, signaling that a WCF-Service wants to be added. Before that happens, check if the service is already in the database, if so, check if something has changed and update, if not just add it or reload it from the database
- Routing happens based on filtering endpoint addresses
- The filter-table and filters will be added dynamically per request
- The contracts will be dynamically added using contract descriptions
- A separate assembly that contains the service contract will be downloaded from blob-storage and used for the contract descriptions
- The dynamic update functionality will be implemented using a custom behavior and a custom extension implementation
All the requirements are packed into a custom IExtension<T> implementation that can be found in the System.ServiceModel namespace (System.ServiceModel.dll). The IExtension<T>-Interface allows to extend
- System.ServiceModel.IExtensibleObject<T>
- System.ServcieModel.IContextChannel
- System.ServiceModel.ServiceHost (That’s what happens here)
- System.ServiceModel.InstanceContext
- System.ServiceModel.OperationContext
using the extensible object pattern. You can read more about it here: IExtension<T> Interface on MSDN
Read more about extensible objects and the the “ExtensibleObject<T> Pattern” here.
To make the extension available to our WCF-Routing service, we make it available though a service-behavior that we can add to our WCF-Routing service at runtime. Behaviors are basically WCF configuration elements, that allow to extend specific WCF objects and add additional functionality.
Personally I see service-behavior like a kind of “bootstrapper” that allows you to add “plugins” (extensions) to a WCF-Service at runtime (this is what I use them for). A service-behavior exposes the service-host if you implement the IServiceBehaviour interface, so that you can add extensions to that specific host. The “Hook” we can use to add an extension to the current service host is by implementing the IServiceBehavior interface and to derive from the abstract class BehaviourExtensionElement (represents a WCF configuration element). Then we can use the ApplyDispatchBehaviour method (comes from IServiceBehavior) and add our extension to the service-host.
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
using SelfUpdatingServiceRouter.Extensions;
namespace SelfUpdatingServiceRouter.Behaviours
{
public class RoutingUpdateBehaviour : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return typeof(RoutingUpdateBehaviour); }
}
protected override object CreateBehavior()
{
return new RoutingUpdateBehaviour();
}
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
RouterUpdateExtension updateExtension = new RouterUpdateExtension();
serviceHostBase.Extensions.Add(updateExtension);
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
}
}
The extension for our service-host (which in this case is the WCF-Routing service) is realized by implementing the IExtension<ServiceHostBase> interface and the IDisposable interface. This is the most powerful piece of code in the whole solution. Maybe this is the most comprehensive example on how to configure a WCF-Routing service dynamically using code only.
using Microsoft.Experience.CloudFx.Framework.Configuration;
using Microsoft.Experience.CloudFx.Framework.Messaging;
using Microsoft.Experience.CloudFx.Framework.Storage;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Routing;
using System.Text;
using System.Threading.Tasks;
namespace SelfUpdatingServiceRouter.Extensions
{
class RouterUpdateExtension : IExtension<ServiceHostBase>, IDisposable
{
private RoleInstanceEndpoint endpointAzure;
ServiceHostBase owner;
IObserver<RouteMeModel> modelObserver;
ServiceBusPublishSubscribeChannel pubSubChannel;
List<ServiceEndpoint> serviceEndPoints;
RoutingConfiguration rc;
public void Attach(ServiceHostBase owner)
{
this.owner = owner;
endpointAzure = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["RoutingServiceMain"];
this.Init();
}
public void Detach(ServiceHostBase owner)
{
this.Dispose();
}
public void Dispose()
{
}
private void Init()
{
this.serviceEndPoints = new List<ServiceEndpoint>();
this.rc = new RoutingConfiguration();
this.AddRoutingEntries();
this.SetupServiceBus();
}
private void AddRoutingEntries()
{
using (var ctx = new RouteContext())
{
if (ctx.Services.Count() > 0)
{
foreach (var entry in ctx.Services)
{
AddServiceBusEntry(entry);
}
}
}
}
private void SetupServiceBus()
{
var config = CloudApplicationConfiguration.Current.GetSection<ServiceBusConfigurationSection>(ServiceBusConfigurationSection.SectionName);
pubSubChannel = new ServiceBusPublishSubscribeChannel(config.Endpoints.Get(config.DefaultEndpoint));
CreateSubscriptionForService(pubSubChannel, "RoutingService");
}
private void CreateSubscriptionForService(ServiceBusPublishSubscribeChannel pubSubChannel, string serviceName)
{
var filter = FilterExpressions.GroupOr(
FilterExpressions.MatchTo(serviceName),
FilterExpressions.MatchTo("ServiceRouter"));
modelObserver = Observer.Create<RouteMeModel>(msg =>
{
var exists = CheckIfRoutingEntryExists(msg);
if (!exists)
{
AddNewServiceEntry(msg);
}
});
pubSubChannel.Subscribe(serviceName,modelObserver,filter);
}
private void AddNewServiceEntry(RouteMeModel msg)
{
using (var ctx = new RouteContext())
{
ctx.ChangeTracker.DetectChanges();
msg.SericeUID = Guid.NewGuid().ToString();
ctx.Services.Add(msg);
ctx.SaveChanges();
AddServiceBusEntry(msg);
}
}
private bool CheckIfRoutingEntryExists(RouteMeModel msg)
{
using (var ctx = new RouteContext())
{
var entry = (from service in ctx.Services
where service.ServiceName.Equals(msg.ServiceName) && msg.ContractName.Equals(msg.ContractName)
select service).FirstOrDefault();
if(entry == null)
{
return false;
}
else
{
return true;
}
}
}
private void AddServiceBusEntry(RouteMeModel message)
{
var storageConnection = CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");
var cloudStorage = new ReliableCloudBlobStorage(StorageAccountInfo.Parse(storageConnection));
var contractAssemblyContainer = CloudConfigurationManager.GetSetting("AssemblyContainerName");
var contractAssemblyName = CloudConfigurationManager.GetSetting("ContractAssemblyName");
byte[] data = null;
using (MemoryStream mstream = new MemoryStream())
{
var gotIt = cloudStorage.Get(contractAssemblyContainer, contractAssemblyName, mstream);
if (gotIt)
{
data = mstream.ToArray();
}
}
var assembly = Assembly.Load(data);
Type contractType = assembly.GetType(message.ContractName);
var conDesc = ContractDescription.GetContract(contractType);
var HTTPbinding = new BasicHttpBinding();
var currentServiceEndPoint = new ServiceEndpoint(
conDesc,
HTTPbinding,
new EndpointAddress(message.EndPointAddress));
currentServiceEndPoint.Name = message.ServiceName;
var routerMainEndpoint = owner.Description.Endpoints.Where(ep => ep.Name == "RouterMain").FirstOrDefault();
var conDescRouter = ContractDescription.GetContract(typeof(IRequestReplyRouter));
var rEndPoint = new ServiceEndpoint(conDescRouter,new BasicHttpBinding(), new EndpointAddress( routerMainEndpoint.Address.Uri.OriginalString +"/" + message.ServiceName));
rEndPoint.Name = message.ServiceName;
this.owner.AddServiceEndpoint(rEndPoint);
var addressFilter = new EndpointAddressMessageFilter(new EndpointAddress(routerMainEndpoint.Address.Uri.OriginalString + "/" + message.ServiceName));
rc.RouteOnHeadersOnly = false;
rc.FilterTable.Add(addressFilter, new List<ServiceEndpoint>() { currentServiceEndPoint });
this.owner.Extensions.Find<RoutingExtension>().ApplyConfiguration(rc);
}
private static bool CheckIfEndPointWasAdded(ServiceHostBase host, string endpointAddress)
{
bool isPresent = false;
foreach (var endpoint in host.Description.Endpoints.ToList())
{
if (endpoint.ListenUri.AbsoluteUri.Equals(endpointAddress))
{
isPresent = true;
break;
}
}
return isPresent;
}
}
}
The main players in this implementation are the following methods:
- AddRoutingEntries and AddServiceBusEntry, both responsible for adding endpoint-entries to the database
- SetupServiceBus and CreateSubscriptionForService, both responsible for setting up a CloudFx driven Service Bus Topic subscription
- AddServiceBusEntry (should be renamed), responsible for the dynamic WCF-Router configuration
Let’s take some of the methods out and focus on their implementation. First the methods that are responsible for the CloudFx setup.
The SetupServiceBus-Method
This method is loading the CloudFx Service Bus configuration using the CloudFx specific CloudApplicationConfiguration class. After loading the configuration, it sets up a ServicePublishSubscribeChannel using the default configuration endpoint to load the Service Bus configuration from the app.config file. The ServicePublishSubscribeChannel can be used to publish messages for a specific Service Bus topic, or to subscribe for messages on a specific Service Bus topic. In this case we do a subscription and wait for topic-messages to arrive.
private void SetupServiceBus()
{
var config = CloudApplicationConfiguration.Current.GetSection<ServiceBusConfigurationSection>(ServiceBusConfigurationSection.SectionName);
pubSubChannel = new ServiceBusPublishSubscribeChannel(config.Endpoints.Get(config.DefaultEndpoint));
CreateSubscriptionForService(pubSubChannel, "RoutingService");
}
The CreateSubscriptionForService-Method
That’s where the real magic of CloudFx happens. First we set-up a filter-expression for the incoming messages. We receive all messages that contain either the name of the service OR the string constant “ServiceRouter”. All other messages are not of any interest. Then we create an Observer<RouteModel>. The observer will check the Service Bus topic for appropriate messages using the filter-criteria’s we defined before. The last thing we do is to subscribe to our pub/sub channel using the service-name (channel name) our observer and the filter. From now on the message-loop is running, ready to receive messages asynchronously. That’s pretty cool!
private void CreateSubscriptionForService(ServiceBusPublishSubscribeChannel pubSubChannel, string serviceName)
{
var filter = FilterExpressions.GroupOr(
FilterExpressions.MatchTo(serviceName),
FilterExpressions.MatchTo("ServiceRouter"));
modelObserver = Observer.Create<RouteMeModel>(msg =>
{
var exists = CheckIfRoutingEntryExists(msg);
if (!exists)
{
AddNewServiceEntry(msg);
}
});
pubSubChannel.Subscribe(serviceName,modelObserver,filter);
}
The AddServiceBusEntry-Method
This methods implements some real cool things:
- Loading the contract assembly for the services to be added to the WCF-Router from Azure Blob storage using CloudFx!
- It adds the endpoint addresses of the WCF-Services to route to as well as the filters to the WCF-Routing service and re-configures the WCF-Routing service at runtime!
To download the contract assembly from Azure Blob storage we need only three lines of code:
The following line loads the configuration settings (the Service Bus connection string) from our configuration file
var storageConnection = CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");
This line creates a new ReliableCloudBlobStorage instance:
var cloudStorage = new ReliableCloudBlobStorage(StorageAccountInfo.Parse(storageConnection));
and this line downloads the contract-assembly blob, using the ReliableCloudBlobStorage instance:
var gotIt = cloudStorage.Get(contractAssemblyContainer, contractAssemblyName, mstream);
It returns true, if the download was successful, otherwise false. That’s all the code required to download a blob. To upload a blob, you use the Put-Method :)
private void AddServiceBusEntry(RouteMeModel message)
{
var storageConnection = CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");
var cloudStorage = new ReliableCloudBlobStorage(StorageAccountInfo.Parse(storageConnection));
var contractAssemblyContainer = CloudConfigurationManager.GetSetting("AssemblyContainerName");
var contractAssemblyName = CloudConfigurationManager.GetSetting("ContractAssemblyName");
byte[] data = null;
using (MemoryStream mstream = new MemoryStream())
{
var gotIt = cloudStorage.Get(contractAssemblyContainer, contractAssemblyName, mstream);
if (gotIt)
{
data = mstream.ToArray();
}
}
var assembly = Assembly.Load(data);
Type contractType = assembly.GetType(message.ContractName);
var conDesc = ContractDescription.GetContract(contractType);
var HTTPbinding = new BasicHttpBinding();
var currentServiceEndPoint = new ServiceEndpoint(
conDesc,
HTTPbinding,
new EndpointAddress(message.EndPointAddress));
currentServiceEndPoint.Name = message.ServiceName;
var routerMainEndpoint = owner.Description.Endpoints.Where(ep => ep.Name == "RouterMain").FirstOrDefault();
var conDescRouter = ContractDescription.GetContract(typeof(IRequestReplyRouter));
var rEndPoint = new ServiceEndpoint(conDescRouter,new BasicHttpBinding(), new EndpointAddress( routerMainEndpoint.Address.Uri.OriginalString +"/" + message.ServiceName));
rEndPoint.Name = message.ServiceName;
this.owner.AddServiceEndpoint(rEndPoint);
var addressFilter = new EndpointAddressMessageFilter(new EndpointAddress(routerMainEndpoint.Address.Uri.OriginalString + "/" + message.ServiceName));
rc.RouteOnHeadersOnly = false;
rc.FilterTable.Add(addressFilter, new List<ServiceEndpoint>() { currentServiceEndPoint });
this.owner.Extensions.Find<RoutingExtension>().ApplyConfiguration(rc);
}
The publisher part
The publishing part is implemented in a separate assembly called “ServiceMessenger”. It contains only one class, the Messenger class. This assembly needs to be referenced by any of the WCF-Services that want to add their endpoints to the WCF-Router.
using Microsoft.Experience.CloudFx.Framework.Configuration;
using Microsoft.Experience.CloudFx.Framework.Messaging;
using Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceMessenger
{
public class Messenger
{
private RouteMeModel model;
public Messenger(string endPointAddress, string contracName, string assemblyName, string serviceName)
{
this.model = new RouteMeModel()
{
ContractName = contracName,
EndPointAddress = endPointAddress,
FullAssemblyName = assemblyName,
ServiceName = serviceName
};
}
public void SendMessageToRouter()
{
var config = CloudApplicationConfiguration.Current.GetSection<ServiceBusConfigurationSection>(ServiceBusConfigurationSection.SectionName);
var routerServiceCtx = new RoutingMessageContext { To = "ServiceRouter" };
using (var pubSubChannel = new ServiceBusPublishSubscribeChannel(config.Endpoints.Get(config.DefaultEndpoint)))
{
pubSubChannel.Settings.MessageTimeToLive = TimeSpan.FromSeconds(120);
pubSubChannel.Publish(this.model, routerServiceCtx);
}
}
}
}
Creating a new instance allows you to pass all the important parameters to create a new routing-entry and send it via the Service Bus to the WCF-Routing service, where the RouterUpdate extension will receive the data and add a new service endpoint, if it not already exists. The initialization process to create a new ServcieBusPublishSubscriber channel is identical with the one we used to subscribe to the Service Bus topic.
public void SendMessageToRouter()
{
var config = CloudApplicationConfiguration.Current.GetSection<ServiceBusConfigurationSection>(ServiceBusConfigurationSection.SectionName);
var routerServiceCtx = new RoutingMessageContext { To = "ServiceRouter" };
using (var pubSubChannel = new ServiceBusPublishSubscribeChannel(config.Endpoints.Get(config.DefaultEndpoint)))
{
pubSubChannel.Settings.MessageTimeToLive = TimeSpan.FromSeconds(120);
pubSubChannel.Publish(this.model, routerServiceCtx);
}
}
There are two differences:
- A RoutingMessageContext instance is created, and the receiver is set via the “To” property
- The Publish-Method of the ServiceBusPublishSubscriberChannel is used to send an instance of the RouteModel class over the wire, which contains the endpoint data of the service that wants to be added to the WCF-Routing service
That’s all that needs to be done to publish a message for a specific Service Bus topic using CloudFx!
The sample WCF-Services and the WCF-Test Client
There are two WCF-Services that publish their endpoints using the ServiceMessenger assembly to the WCF-Router:
- HelloWorld
- and HelloWorldExtended
Each of them implements a simple service-method that returns a string. Both services have one endpoint and one metadata publishing behavior.
Both service interfaces are defined in the ContractAssembly that has been uploaded to local storage for testing (this is what you should do as well, if you want to test the solution).
The services are hosted in separate worker roles, that are nearly identical. Except for the service endpoints:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using System.ServiceModel;
using ContractAssembly;
using ServiceMessenger;
using System.ServiceModel.Description;
namespace HelloWorld
{
public class WorkerRole : RoleEntryPoint
{
private string endPointAddress;
private string listenAddress;
private RoleInstanceEndpoint endpointAzure;
public override void Run()
{
using (ServiceHost host = new ServiceHost(typeof(HelloWorld)))
{
Thread.Sleep(60000);
Trace.TraceInformation("HelloWorldExtended entry point called", "Information");
this.ConfigureServiceHost(host);
while (true)
{
Thread.Sleep(1000);
Trace.TraceInformation(endPointAddress);
}
}
}
private void ConfigureServiceHost(ServiceHost host)
{
try
{
endpointAzure = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["HelloWorldMain"];
this.endPointAddress = string.Format("http://{0}/HelloWorld", endpointAzure.IPEndpoint);
this.listenAddress = string.Format("http://{0}:{1}/HelloWorld/", RoleEnvironment.GetConfigurationSettingValue("Domain"), endpointAzure.IPEndpoint.Port);
var httpBinding = new BasicHttpBinding();
httpBinding.SendTimeout = TimeSpan.FromMinutes(1);
httpBinding.ReceiveTimeout = TimeSpan.FromMinutes(1);
var helloWorldExtendedEndPoint = host.AddServiceEndpoint(typeof(IHelloWorldService), httpBinding, this.endPointAddress, new Uri(this.listenAddress));
helloWorldExtendedEndPoint.Name = "HelloWorld";
var messenger = new Messenger(this.endPointAddress, "ContractAssembly.IHelloWorldService", "ContractAssembly.dll", "HelloWorld");
messenger.SendMessageToRouter();
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(this.endPointAddress);
host.Description.Behaviors.Add(smb);
host.Open();
}
catch (Exception ex)
{
Trace.TraceError(ex.Message);
}
}
public override bool OnStart()
{
ServicePointManager.DefaultConnectionLimit = 12;
return base.OnStart();
}
}
}
To publish the single endpoint of each service we use two lines of code:
var messenger = new Messenger(this.endPointAddress, "ContractAssembly.IHelloWorldService", "ContractAssembly.dll", "HelloWorld");
messenger.SendMessageToRouter();
We create a new Messenger instance and publish the new service endpoint using the SendMessageToRouter method. Sweet!
For a quick overview of the solution, please see this code-map:
Well, that’s it! Thank you for taking the time visiting my blog, and I hope you enjoyed reading this article!
Code on GitHub
The post Self updating WCF Routing Service on Windows Azure using CloudFx appeared first on @awsomedevsigner.
Working as professional freelancer for the last 5 years. Specialized on and addicted to .NET and a huge fan of Windows Azure from the beginning. Socializing people of all areas, from CEO's to co-workers. Consider myself as a social architect.
Now the proud owner of ExGrip LLC - building mobile cloud experiences. Our latest product "Tap-O-Mizer" is shortly in Beta2. It enables you to see what really needs to be changed in your Windows Phone 8 or Windows 8 app (in real-time, if needed), to ensure customer satisfaction.
Started authorship for "Pluralsight - Hardcore Developer Training" and going to publish the first course on Windows Azure soon.
A few years ago I made a major shift from developer to "devsigner".Focusing my creativity also on great user experiences on Windows, Windows 8 and Windows Phone. Utilizing Expression Design, Expression Blend, Photoshop and Illustrator.
I started developing my first programs on a Commodore C64 (basic and assembly) at the age of fourteen years. Later on an Amiga 500 (C++). After that I made the shift to DOS and Windows from version 3.11 and up.
To me the most important part of developing new experiences is to work with creative and outstanding people and to bring new, exciting ideas to life.
I strongly believe that everyone can be a hero if he/she get's pushed and motivated and valued. Therefore, and that under any circumstances: "People first!"
Specialties:Extremely motivated and pushing people to create results.