Introduction
WCF routing is simple even if it is usually ranked as an advanced topic.
It depends greatly on your understanding of Filters, however implementing routing in .NET is really simple, let it be with configurations or within code.
Background
WCF routing is not known as a load balancer.
Load balancing can be complicated based on its type, it can be via software or hardware.
It can be either static or dynamic.
It can be based on location, requester, request or many other details or criteria.
This example is a trivial one, just to show that simple use of custom Message filter can balance requests on 10 different instances of the service sequentially.
It looks too simple that it can't be used outside tutoring.
But I am sure if you add more ideas and efforts to it, it can be a kernel to have a real load balancer.
Benefits of WCF Routing
- Content-Based Routing
- Protocol Bridging
- Error Handling
How It Works
- Create your service (Jotter)
- Expose and endpoint ep1
- Host your service
- Create another service (routing)
- Expose another endpoint ep2
- Add routing behavior to the routing service.
- Host the routing service.
- Create a client that consumes service Jotter but through ep2.
- In order to be able to add the routing behavior, you need to add
FilterTable
in which you add Filters.
Types of Filters
Action
EndpointAddress
EndpointAddressPrefix
And
Custom
EndpointName
MatchAll
XPath
Using the Code
The code is in 2 parts.
The first project shows a simplest example of routing.
The second routerPlus
is the load balancer.
This is the simplest code for routing:
static void Main(string[] args)
{
using (var serviceHost = new ServiceHost(typeof(Jotter)))
{
serviceHost.Open();
var routerEndpoint = new EndpointAddress("net.pipe://localhost/router");
using (var routerHost = new ServiceHost(typeof(RoutingService)))
{
routerHost.Open();
var proxy = ChannelFactory<IJotter>.CreateChannel(new NetNamedPipeBinding(), routerEndpoint);
while (!Console.KeyAvailable)
Console.WriteLine(proxy.GetString());
}
}
}
The configurations are so important:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior" >
<serviceDebug includeExceptionDetailInFaults="true"/>
<routing filterTableName="myFilterTable"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="System.ServiceModel.Routing.RoutingService"
behaviorConfiguration="ServiceBehavior" >
<endpoint address="net.pipe://localhost/router"
binding="netNamedPipeBinding"
contract="System.ServiceModel.Routing.IRequestReplyRouter"></endpoint>
</service>
<service name ="WcfRouting.Jotter" >
<endpoint address="net.pipe://localhost/Jotter"
binding="netNamedPipeBinding" name="MainServiceEndPoint"
contract="WcfRouting.IJotter"></endpoint>
</service>
</services>
<client>
<endpoint name="serviceEndpoint"
address ="net.pipe://localhost/Jotter" contract="*"
binding="netNamedPipeBinding"></endpoint>
</client>
<routing>
<filterTables>
<filterTable name="myFilterTable">
<add filterName="MatchAll" endpointName="serviceEndpoint"/>
</filterTable>
</filterTables>
<filters>
<filter name="MatchAll" filterType="MatchAll"/>
</filters>
</routing>
</system.serviceModel>
The Load Balancer
As we saw above, there are various types of filters through which we are able to change the route of the requests by 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. We can also create custom filters by creating our own implementation of MessageFilter
.
Now having said that, and having known how to make a simple router, let's think of a way to extend this routing functionality to distribute requests equally on multiple endpoints.
For this purpose, we did the following:
- Create a service (Jotter)
- Host this service in 10 different hosts
- Expose 10 different endpoints one to each host (Using
NetNamedPipeBinding
) - Create the routing service
- Expose the routing service through one endpoint. (Using
NetTcpBinding
) - Add the routing behavior to the routing service and add our custom
MessageFilter
- Create the Client, that consumes to the router's endpoint and the service Contract
Now for each request, the service will tell us what endpoint was used.
We will see that the load was evenly and equally distributed on all the hosts.
These hosts can be on different servers, where the router will send the requests to, however, beware of the binding you are using when testing this on different servers. NetNamedPipeBinding
is not suitable then.
The load Balancer is shown here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Routing;
using WcfRouting;
namespace WCFRoutingPlus
{
class Program
{
private static int maxHostNumber = 10;
static void Main(string[] args)
{
int index = 0;
List<ServiceHost> lstServers = new List<ServiceHost>(maxHostNumber);
for (int i = 0; i < maxHostNumber; i++)
{
lstServers.Add(new ServiceHost(typeof(Jotter)));
}
lstServers.ToList().ForEach(
x =>
{
x.AddServiceEndpoint(typeof(IJotter), new NetNamedPipeBinding(),
string.Format("net.pipe://localHost/services/HostEndPoint{0}", ++index));
x.Open();
});
try
{
var serverEndpoints = new List<ServiceEndpoint>();
lstServers.ForEach(x =>
{
serverEndpoints.AddRange(x.Description.Endpoints);
}
);
Console.WriteLine("Hit <ENTER> to Move Next!!");
Console.WriteLine("Hit <Any Other Key> to Terminate!!");
using (var router = new ServiceHost(typeof(RoutingService)))
{
string routerAddress = "net.tcp://localhost:8000/router/routerendpoint";
router.AddServiceEndpoint(typeof(IRequestReplyRouter),
new NetTcpBinding(), routerAddress);
var config = new RoutingConfiguration();
LoadBalancerFilter.EndpointsNumber = maxHostNumber;
serverEndpoints.ForEach(x =>
config.FilterTable.Add(new LoadBalancerFilter(), new[] { x }, index--));
router.Description.Behaviors.Add(new RoutingBehavior(config));
router.Open();
var client = ChannelFactory<IJotter>.CreateChannel
(new NetTcpBinding(), new EndpointAddress(routerAddress));
while (Console.ReadKey().Key == ConsoleKey.Enter)
{
Console.WriteLine(client.GetString());
}
router.Close();
}
lstServers.ForEach(x => x.Close());
}
catch (Exception)
{
lstServers.ForEach(x => x.Abort());
}
}
}
}
The Custom MessageFilter
looks like:
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace WCFRoutingPlus
{
class LoadBalancerFilter:MessageFilter
{
private static int _numberOfRejections = 0;
private static int _Index = 0;
private static int _numberOfEndpoints;
public static int EndpointsNumber {
set { _numberOfEndpoints = value; }
}
public override bool Match(Message mesage)
{
bool result = true;
if (_numberOfRejections >= _Index)
{
result = true;
_Index++;
_numberOfRejections = 0;
}
else
{
result = false;
_numberOfRejections++;
}
if (_Index >= _numberOfEndpoints)
_Index = 0;
return result;
}
public override bool Match(MessageBuffer buffer)
{
var message = buffer.CreateMessage();
return true;
}
}
}
Result
To Do
- Download the example, make sure you understand what is meant by this loadbalancing.
- Come with new ideas and post them as comments and let's work on a good project.
History
- 17th January, 2014: Initial post