Implementing a message pass-through WCF Behaviour (Router WCF service)
I recently came across a requirement to implement a Windows Communication Service that acts as a message router across several other WCF backend services. This router service main functionality is to encapsulate the backend services and also to add automatic request monitoring. This should not be hard I was thinking but there are couple of twists, first the router service should not sensitive to the services themselves and how they change and communicate with their clients. It also will be handing encrypted messages that it cannot decrypt as these messages belong to other partners that we do not control and of course do not have the certificate used for encryption.
The way this is done is by implementing custom WCF message dispatch inspector and custom WCF message client message inspector behaviours. Why do we need both as our service acts as a WCF to all the clients and as a client to all the backend services. So the main idea is to when a message is received over the wire the entire message body is escaped and placed in a custom header element and before this message is sent to the backend the original message body is extracted from the header and written back to the message sent. While the response is being received from the backend service the same logic happens but in the reverse order. So the reply is written to the custom header as an escaped string and then before this reply is delivered to the original caller the reply is read from the header and sent to the client. Simple right J well it will get even better; since we want the original message unparsed and not changed coming on the wire this has to be done using a custom message encoder for both ways, receiving requests from the clients and receiving responses from the backend services. So to try to make this simple I placed the following diagram.
So you need to implement a custom message encoder inheriting from the class “MessageEncoder” and mainly implement the method “ReadMessage” to be as follows.
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
byte[] msgContents = new byte[buffer.Count];
Array.Copy(buffer.Array, buffer.Offset, msgContents, 0, msgContents.Length);
bufferManager.ReturnBuffer(buffer.Array);
MemoryStream stream = new MemoryStream(msgContents);
Message message = ReadMessage(stream, int.MaxValue);
string ns = "https://Somenamespace";
MessageHeader header = MessageHeader.CreateHeader("OriginalFullMessage", ns, UTF8Encoding.UTF8.GetString(msgContents));
message.Headers.Add(header);
return message;
}
So you need to implement first a custom class inheriting from “IDispatchMessageInspector” and it should first override the method “ApplyDispatchBehavior” to add this dispatch behavior to the endpoint as follows.
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
endpointDispatcher.AddressFilter = new System.ServiceModel.Dispatcher.MatchAllMessageFilter();
}
Then you need to implement the method “BeforeSendReply” to put the original reply back on the wire as follows.
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
string origMsg = OperationContext.Current.RequestContext.RequestMessage.ToString();
string fullMessageHeader = "OriginalFullMessage";
string ns = "https://Somenamespace";
int fullMessageHeaderIndex = reply.Headers.FindHeader(fullMessageHeader, ns);
if (fullMessageHeaderIndex >= 0)
{
origMsg = UnescapeXml((reply.Headers.GetHeader<string>(fullMessageHeaderIndex)));
}
Message newreply = Message.CreateMessage(MessageVersion.None, reply.Headers.Action, new SimpleMessageBody(origMsg));
newreply.Headers.To = reply.Headers.To;
reply = newreply;
return;
}
You will then need to implement the method “BeforeSendRequest” from the class “IClientMessageInspector” to put the original request back on the wire before we send it to the backend services.
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
string origMsg = OperationContext.Current.RequestContext.RequestMessage.ToString();
string fullMessageHeader = "OriginalFullMessage";
string ns = "https://Somenamespace";
int fullMessageHeaderIndex = request.Headers.FindHeader(fullMessageHeader, ns);
if (fullMessageHeaderIndex >= 0)
{
origMsg = UnescapeXml((request.Headers.GetHeader<string>(fullMessageHeaderIndex)));
}
Message newRequest = Message.CreateMessage(MessageVersion.None, request.Headers.Action, new SimpleMessageBody(origMsg));
newRequest.Headers.To = request.Headers.To;
request = newRequest;
return null;
}
And that’s it J
Comments
Anonymous
January 12, 2012
Hey Malek, Can you share your code for Paas through Router as I am having one client application and one WCF service of BasicHTTPBinding, but I want to put one extra Rourter service between these two applications. Please if possible send me code,Anonymous
December 30, 2012
Hi, can you share your code for pass through Router Service. Please send me at anishnjain@gmail.com Thank youAnonymous
October 02, 2014
I would also be interested in see the code, I have been trying to dynamically create a wcf service inside an HTTPModule and then pass the current request through to the newly created wcf service.Anonymous
January 18, 2015
Dear, Can you share the code to ahmed.goher@gmail.com