Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

WCF Intermediate Service between Client and Server

3.77/5 (9 votes)
29 Sep 2008CPOL5 min read 1   763  
This article describes how to create a load balancer, routing WCF service between client and WCF server service

Introduction

The purpose of this article is to create a WCF service which can work as load balancer or router. Hosting a WCF service requires creating the service contract, Data contract and configuring end points, hosting the service, generating the Web Service Description Language(WSDL), enabling metadata exchange point so that client can create the proxy. Both the client and server channel should agree on addressing, binding and message filtering. Sometimes the product requires some additional features like logging, routing, load balancing or introducing a security boundary. We can easily introduce intermediate service and achieve these kinds of features by some tweaking in addressing and filtering behavior.

Background

The reader should be aware of WCF basics.

Using the Code

The attached source contains the following projects: 

  • TargetServiceContract: Contract for the target WCF service
  • TargetHost: Host console application for target WCF Service
  • IntermediateServiceContract: Service contract which can work as load balancer or router
  • IntermediateServiceHost: Host console application which hosts the IntermediateServiceContract
  • Client: The client project which consumes the WCF target service

Using the operation type in an operation signature doesn't by definition mean that any message can be processed by the service. By default Action header is used to target the particular operation. The service model inspects incoming message and looks for an operation on the endpoint that has a matching action header. If it can't find a match, the request is rejected.

When would action headers not match the operation being invoked? One possibility is when an intermediary or routing service is placed between the client and the application service. In this scenario, the client application doesn't know of the presence of the intermediate service and the action header targets the application service.

Action header indicates the URI for the operation to be invoked. It is generated by concatenating the service contract namespace with the service contract friendly name and the operation name.

http://Example/IntermediateService/TargetServiceContract/VerifyMessage

The reply action appends “Response” to this.

http://Example/IntermediateService/TargetServiceContract/VerifyMessageResponse 

Client can generate the same Action, ReplyAction and proxy setting by generating the service proxy. Here is the code which is generated from the proxy.

C#
[System.ServiceModel.OperationContractAttribute(
    Action="http://Example/IntermediateService/TargetServiceContract/VerifyMessage",
    ReplyAction=
      "http://Example/IntermediateService/TargetServiceContract/VerifyMessageResponse")]
 string VerifyMessage(string message); 

The client binding configuration will reflect physical address. Here is the client endpoint setting which has the physical address as the address of the target service. We can generate it by creating the service reference by using the metadata endpoint("http://localhost:8001/TargetService/Mex").

XML
<endpoint address="http://localhost:8001/TargetService"
                  binding="wsHttpBinding"
                  bindingConfiguration="wsHttpEndPoint"
                  contract="TargetServiceClient.TargetServiceContract"
                  name="wsHttpEndPoint"
                  behaviorConfiguration="clientBehavior" />

When client sends any message to service, the message is sent to physical address with action mentioned in the generated proxy file.

Intermediate Service

Intermediate service is designed to receive messages that can target any service and it must be able to forward the original message to the appropriate service without any change in the body of the message. The intermediate service may look at only the message headers but the original message elements should be forwarded without any change.

C#
[OperationContract(Name="ProcessMessage",Action="*",ReplyAction="*")]
Message ProcessMessage(Message message);

In case if the Action and ReplyAction is * then all the messages are dispatched to the same operation and only one operation can have Action and ReplyAction as * in the service contract. Since Channel dispatcher always gets the input argument as Message type(defined in System.ServiceModel.Channels) and returns the value as Message type so the intermediate service should have operation which has Input argument and return type as Message. This operation can work as target of any request.

Forwarding Message to Original Service

Service proxy usually has strongly typed operation but in intermediate service we use a generic proxy operation which can send any type as input argument and receive any type as response. Since the contract has an untyped message, the same message can be sent to the service and the response can be directly sent to the client. Intermediate service doesn't do any other change except changing the To address to send the message to the appropriate service.

Sending the Request to Intermediate Service

When an intermediate service is introduced, it is always best if the client can send messages using the correct To header for the service while still sending the message to the intermediate service. The first way to achieve this is to configure ClientViaBehavior as mentioned below. This tells the client to generate the message which would have the To header as the end point service address but it would go via the intermediate service.

XML
<client>
            <endpoint address="http://localhost:8002/IntermediateServiceExample" 
                      binding="wsHttpBinding"
                      bindingConfiguration="wsHttpEndPoint"
                      contract="TargetServiceClient.TargetServiceContract"
                      name="wsHttpEndPoint" behaviorConfiguration="clientBehavior">
            </endpoint>
        </client>
      <behaviors>
        <endpointBehaviors>
          <behavior name="clientBehavior">
            <clientVia viaUri="http://localhost:8002/IntermediateServiceExample"/>
          </behavior>
        </endpointBehaviors>
      </behaviors>

The other way to achieve this is to mention the listenuri in the target service so that the logical address of the target service can be the address of intermediate service and physical address can be the endpoint address. In this case when the client generates the proxy, it would have the address as the address of the intermediate service.

XML
<endpoint address="http://localhost:8002/IntermediateServiceExample"
          binding="wsHttpBinding"
          bindingConfiguration="wsHttpBinding" name="wsHttpEndPoint"
          contract="IntermediateServiceExample.ITargetServiceContract"
          listenUri="http://localhost:8001/TargetService" />

In this scenario, the service must be aware of the intermediate service which I think is wrong so it is better to tell the intermediate service address to the client and client can use the first way. The client sends the message to intermediate service with To header as the target service address. Thus, the To header will never match the router's logical address. By default, services use the EndpointAddressMessageFilter to determine whether a message's To header matches any of its configured endpoints. Since a router can't expect this to work, it should install the MatchAllMessageFilter. We can achieve this by using the following filter. It passes all to header messages.

C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
                 AddressFilterMode = AddressFilterMode.Any)]
public class IntermediateServiceManager : IIntermediateServiceContract

Now when the client sends any message, it would go via the intermediate service. The intermediate service can load balance it or do the routing, send the request to appropriate service, get the response and return the same response to the client. Since all the requests from the client and all the responses from the target service would go via the intermediate service, we can do logging, tracing for each and every message going through the channel.

History

  • 29th September, 2008: Initial post

License

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