Extending Dublin's forwarding service with a custom WCF message filter

In a previous post we showed how to implement a basic WCF content based routing solution using the Windows Application Server (Dublin) forwarding service together with XPath message filters and filter tables. Even though XPath filters are a very appealing mechanism for implementing service brokering or composition solutions, there are a large variety of scenarios that can be addressed more efficiently using other filtering techniques. Trying to tackle this large spectrum of scenarios using a fixed set of filtering mechanisms such as XPath or XQuery is precisely one of the main challenges faced by traditional message brokering frameworks such as the infamous Enterprise Service Buses (ESBs).

Dublin’s forwarding service approaches this challenge using an elegant model for expressing forwarding behaviors by orchestrating arbitrarily complex WCF message filters and filter tables. Conceptually, this model decouples the specific message filtering capabilities (WCF message filters) from the mechanism used for composing those filters in order to achieve the expected message forwarding capabilities (forwarding service). The best part is that this model works natively with any WCF message filter. This allows developers to extend the forwarding service by implementing custom WCF message filters.

Let’s take a look at a practical example using the following sample service.

   1:  public class SampleService : ISampleService
   2:  {
   3:      public string Echo(string msg)
   4:      {
   5:          return msg;
   6:      }
   7:  }

Figure: Sample WCF service

As you can see, the service contract includes a single operation that receives a string as a parameter. Suppose that, in some circumstances, we would like to constraint the size of the message that can be processed by the service endpoint. We can easily achieve that by implementing a custom message filter like the one shown in the following code.

   1:      public class CustomFilter: MessageFilter
   2:      {
   3:          private int minSize;
   4:          private int maxSize;
   5:   
   6:   
   7:          public CustomFilter()
   8:              : base()
   9:          { }
  10:   
  11:          public CustomFilter(string paramlist)
  12:              : base()
  13:          {
  14:              string[] sizes= paramlist.Split(new char[1]{','});
  15:              minSize= Convert.ToInt32(sizes[0]);
  16:              maxSize= Convert.ToInt32(sizes[1]);
  17:          }
  18:   
  19:          public override bool Match(System.ServiceModel.Channels.Message message)
  20:          {
  21:              MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue);
  22:              return Match(buffer);
  23:          }
  24:   
  25:          public override bool Match(MessageBuffer buffer)
  26:          {
  27:              if (buffer.BufferSize > minSize && buffer.BufferSize <= maxSize)
  28:                  return true;
  29:              else
  30:                  return false;
  31:          }
  32:      }

Figure: Custom WCF message filter

Looking at the previous code, you can notice that the filter accepts a message only if its size its within the limits expressed by the mixSize and maxSize properties.

The previous filtering techniques works well for one service endpoint; but what happens in the case that the service is hosted in multiple endpoints with different message size limitations? A simple but not very flexible solution would be to force each client application to interact with a specific service endpoint based on the size of the message this application is sending. A more flexible alternative could be to provide a central service endpoint that can dynamically distribute the message to the correct WCF service by executing different permutations of our WCF message filter. This can be easily done by configuring a custom Dublin’s forwarding service as illustrated in the following code.

   1:  <system.serviceModel>
   2:          <services>

3: <service behaviorConfiguration="forwardingConfig"

name="Microsoft.ProcessServer.Messaging.ForwardingService">

   4:                  <endpoint address="RequestReply" binding="basicHttpBinding" 
                                 name="reqReplyEndpoint"
                                 contract="Microsoft.ProcessServer.Messaging.IRequestReplyDatagram"/>
   5:              </service>
   6:          </services>
   7:          <bindings>
   8:              <basicHttpBinding>
   9:                  <binding name="BasicHttpBinding_ISampleService">
  10:                      <security mode="None">
  11:                          <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
  12:                          <message clientCredentialType="UserName" algorithmSuite="Default"/>
  13:                      </security>
  14:                  </binding>
  15:              </basicHttpBinding>
  16:          </bindings>
  17:          <behaviors>
  18:              <serviceBehaviors>
  19:                  <behavior name="forwardingConfig">
  20:                      <forwardingBehavior filterTableName="contentFilterTable" 
                                               filterOnHeadersOnly="false"/>
  21:                      <serviceMetadata httpGetEnabled="true"/>
  22:                  </behavior>
  23:                  <behavior name="ServiceBehavior">
  24:                      <serviceMetadata httpGetEnabled="true"/>
  25:                      <serviceDebug includeExceptionDetailInFaults="false"/>
  26:                  </behavior>
  27:              </serviceBehaviors>
  28:          </behaviors>
  29:          <client>
  30:              <endpoint address=http://pdc08-csd/SampleService/Service.svc/ep1 
                             binding="basicHttpBinding" 
                             bindingConfiguration="BasicHttpBinding_ISampleService" contract="*" 
                             name="BasicHttpBinding_ISampleService1"/>

31: <endpoint address=http://pdc08-csd/SampleService/Service.svc/ep2

binding="basicHttpBinding"

bindingConfiguration="BasicHttpBinding_ISampleService" contract="*"

name="BasicHttpBinding_ISampleService2"/>

  32:          </client>
  33:          <filtering>
  34:              <filters>

35: <filter name="customFilter1" filterType="Custom"

customType="Tellago.ServiceModel.Samples.FwdServiceCustomFilter.CustomFilter,

Tellago.ServiceModel.Samples.FwdServiceCustomFilter,Version=1.0.0.0,

Culture=neutral, PublicKeyToken=b6e6a71f86cc69e9" filterData="0,1000"/>

  36:                                                                                                                                                                                                                                                  
  37:                  <filter name="customFilter2" filterType="Custom" 

                               customType="Tellago.ServiceModel.Samples.FwdServiceCustomFilter.CustomFilter,

                                           Tellago.ServiceModel.Samples.FwdServiceCustomFilter,Version=1.0.0.0,

                                           Culture=neutral, PublicKeyToken=b6e6a71f86cc69e9" filterData="1001,100000"/>

  38:                                                                                                                                                                                                                                        
  39:              </filters>
  40:              <filterTables>
  41:                  <table name="contentFilterTable">
  42:                      <filters>
  43:                          <add filterName="customFilter1" mappedValue="BasicHttpBinding_ISampleService1"/>
  44:                          <add filterName="customFilter2" mappedValue="BasicHttpBinding_ISampleService2"/>
  45:                      </filters>
  46:                  </table>
  47:              </filterTables>
  48:          </filtering>
  49:      </system.serviceModel>

The previous code creates two instances of our sample WCF message filter with different size limits (lines 35-37). Those filters are later combines as part of a filter table (lines 41-46) and mapped to the service endpoint (lines 30-31) the message should be forwarded to.

The service host’s file for the forwarding service looks like the following.

   1:  <%@ServiceHost Service="Microsoft.ProcessServer.Messaging.ForwardingService"%>

In this scenario, the different client applications can interact with a single endpoint enabled by the forwarding service. After the message is received, the forwarding service will determine the final destination by executing the appropriate WCF message filter.

1 Comment

Comments have been disabled for this content.