As a little experiment I’ve been playing with a WCF router. Basically a
router will accept any request on a certain binding and forward the request to
the real service. There are quite some examples available on the internet but
they were never complete or everything was done in code instead of using the
configuration file.
The service:
This could be any service, no special code is required.
The client:
Any client, no special code required.
I only created a behavior extension and message inspector for adding some
headers with information (binding, contract, isOneWay) for the router before
sending the request. These are “attached” to the endpoint by configuration so
there is no impact on the implementation of the client itself. The binding and
contract headers will be used to determine where the request has to be routed
to. The isOneWay header is needed to distinguish request-reply from one-way
messages, this will be explained in the next component.
The router service:
This is the part that does all the work. The routing is based on the binding
and contract and my implementation is capable of handling standard
request-reply and one way messages. If you don’t distinguish between them the
router will throw timeout exceptions.
To accomplish this obstacle you first need three service contracts for the
router.
[ServiceContract]
public interface IRouter
{
[OperationContract(ReplyAction = "*")]
Message Action(Message msg);
[OperationContract(IsOneWay = true)]
void OneWayAction(Message msg);
}
[ServiceContract]
public interface IGenericContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message Action(Message message);
}
[ServiceContract]
public interface IOneWayGenericContract
{
[OperationContract(Action = "*", IsOneWay = true)]
void Action(Message message);
}
IRouter is the real service contract of the router service, IGenericContract
and IOneWayGenericContract are only used as contracts in the endpoint
configurations to the real service, they do not have an implementation. Also
pay attention to the Action=”*” attribute, without this the WCF infrastructure
will complain about incompatible actions. This is also the reason why we need
to make two separate contracts, you can only have one OperationContract with
Action = “*”.
The second part of the solution to the “standard” vs “one way” request problem
is a DispatchOperationSelector, implemented as an endpoint behavior. This
little critter will return the name of the operation that must be executed on
the router. To make its decision it will check the header “isOneWay” that was
added by message inspector on the client side. If the value of the header is
true it will return “OneWayAction”, corresponding to the IRouter.OneWayAction
method, else it will return “Action”, corresponding to the IRouter.Action
method.
sealed class DispatchOperationSelector : IDispatchOperationSelector, IEndpointBehavior
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.ContractFilter = new MatchAllMessageFilter();
endpointDispatcher.DispatchRuntime.OperationSelector = this;
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
#region IDispatchOperationSelector Members
public string SelectOperation(ref Message message)
{
if (message.Headers.GetHeader<bool>("isOneWay", "http://company.com"))
{
return "OneWayAction";
}
else
{
return "Action";
}
}
#endregion
}
In the configuration file of the router there are 3 important parts
The mapping definition:
<routerServiceMappingxmlns="http://company.com">
<bindings>
<bindingname="nettcpbinding">
<contracts>
<contractname="ServiceContracts.IConcat"endpoint="ConcatService"/>
<contractname="ServiceContracts.ICalc"endpoint="CalcService"/>
</contracts>
</binding>
<bindingname="basichttpbinding">
<contracts>
<contractname="ServiceContracts.ICalc"endpoint="HttpCalcService"/>
</contracts>
</binding>
</bindings>
</routerServiceMapping>
For you information, to create custom configuration sections I use the
Configuration Section Designer plugin
of Jelle Druyts, this is really a must
have.
The service definition:
<services>
<servicename="Router.RouterService"behaviorConfiguration="" >
<endpoint name="RouterService"
address="net.tcp://localhost:3000/Router"
contract="Router.IRouter"
binding="netTcpBinding"
behaviorConfiguration="DispatchBehavior" />
<endpoint name="RouterService"
address="http://localhost:3001/Router"
contract="Router.IRouter"
binding="basicHttpBinding"
behaviorConfiguration="DispatchBehavior" />
</service>
</services>
You’ll need one for each binding you want to support.
The client endpoints:
<client>
<endpoint name="ConcatService"
address="net.tcp://localhost:2000/Concat"
contract="Router.IGenericContract"
binding="netTcpBinding" />
<endpoint name="ConcatService"
address="net.tcp://localhost:2000/Concat"
contract="Router.IOneWayGenericContract"
binding="netTcpBinding" />
<endpoint name="HttpCalcService"
address="http://localhost:2001/Calc"
contract="Router.IGenericContract"
binding="basicHttpBinding" />
<endpoint name="HttpCalcService"
address="http://localhost:2001/Calc"
contract="Router.IOneWayGenericContract"
binding="basicHttpBinding" />
<endpoint name="CalcService"
address="net.tcp://localhost:2002/Calc"
contract="Router.IGenericContract"
binding="netTcpBinding" />
<endpoint name="CalcService"
address="net.tcp://localhost:2002/Calc"
contract="Router.IOneWayGenericContract"
binding="netTcpBinding" />
</client>
You’ll need one for each binding-contract-oneway combination you want to
support.
I know that if you have a lot of services to route this will become a big mess,
in that case you’ll have to look for another solution to “register” your
services with the router. One possible solution is that the services themselves
register with the router. This might seem as if the services need to know that
there is a routing infrastructure but it will actually be the host or maybe an
extension that will take care of the registering, the service implementation
itself does not need to know that it can receive routed messages.
The complete solution is attached to the post, just keep in mind that this is
“proof of concept” code that is not completely optimized, could contain bugs
and is not intended to be used directly in real life applications.
If you have any comments, ideas or improvements on the code please let me
know.
Other WCF articles:
-
MaxItemsInObjectGraph and keeping references when serializing in WCF
- Tracing WCF
messages
- Use your
WCF proxies in a safe way









