February 2009 - Posts

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.

We are hiring

Tellago, Inc (my new venture) is aggressively expanding its services in the U.S. East Coast and particularly in the South East. We are looking for developers and architects skillful with Microsoft Connected Systems technologies such as BizTalk Server, WCF, WF, CardSpace as well as SharePoint Server and SQL Server.

If you meet those requirements and you are looking to be part of a young, growing and energetic firm, play with the latest technologies, collaborate closely with Microsoft's product teams and having the opportunity of expanding your presence in the developer community by authoring papers or presenting at industry conferences please drop me a line at jesus [dot] rodriguez [at] tellago [dot] com or directly to jobs [at] tellago [dot] com.

Using WS-Discovery in WCF 4.0

Runtime endpoint discovery is one of the most challenging capabilities to implement in service oriented systems. Dynamically resolving service’s endpoints based on predefined criteria is a necessary functionality to interact with services which endpoint addresses change frequently. WS-Discovery is an OASIS Standard that defines a lightweight discovery mechanism for discovering services based on multicast messages. Essentially, WS-Discovery enables a service to send a Hello announcement message when it is initialized and a Bye message when is removed from the network. Clients can discover services by multicasting a Probe message to which a service can reply with a ProbeMatch message containing the information necessary to contact the service. Additionally, clients can find services that have changed endpoint by issuing a Resolve message to which respond with a ResolveMatch message.

 

Figure: WS-Discovery conceptual model

Contrary to other WS-* protocols, WS-Discovery has found a great adoption among the network device builders as it allows to streamline the interactions between these type of devices. For instance, a printer can use WS-Discovery to announce its presence on a network so that it can be discovered by the different applications that require printing documents. Windows Vista's contact location system is another example of a technology based on WS-Discovery.

The 4.0 release of Windows Communication Foundation includes an implementation of WS-Discovery that enables service’s endpoints as runtime discoverable artifacts. WCF enables the WS-Discovery capabilities in two fundamental models: Managed and Ad-Hoc. The managed mode assumes a centralized component called service proxy that serves as a persistent repository for all the services in a network. When a service is initialized it publishes its details to the discovery proxy so that it becomes accessible the the different clients in the network.

Contrary to the managed model, the Ad-Hoc mechanism does not rely on a centralized discovery proxy. In this model, services publish their presence in a network by multicasting announcement message that can be processed by the interested consumers. Additionally, clients can also multicast discover messages through the network in order to find a service that matches predefined criteria.

WCF's WS-Discovery managed mode will be the subject on a future post. Today we would like to illustrate the details of enabling dynamic discovery using the WS-Discovery 's Ad-Hoc model in WCF 4.0. This model is traditionally simpler to implement than the managed model although it can introduce some challenges from the management standpoint.

WCF 4.0 abstracts the WS-Discovery Ad-Hoc model using the ServiceDiscoveryBehavior which indicates that a service can be discoverable and the UdpDiscoveryEndpoint that instantiates a service endpoint that can listen for discovery requests. The remaining of this post will provide a practical example of the use of the WS-Discovery Ad-Hoc model in WCF 4.0

Let’s start with the following WCF service.

   1:      public class SampleService: ISampleService
   2:      {
   3:          public string Echo(string msg)
   4:          {
   5:              return msg;
   6:          }
   7:      }
   8:   
   9:      [ServiceContract]
  10:      public interface ISampleService
  11:      {
  12:          [OperationContract]
  13:          string Echo(string msg);
  14:      }

Figure: Sample WCF Service

In order to make the service discoverable we first need to add the ServiceDiscoveryBehavior to the service behavior’s collection. As explained previously, this behavior indicates to the WCF runtime that the service supports the WS-Discovery protocol.

   1:  using (ServiceHost host = new ServiceHost(typeof(SampleService), new Uri(base uri...)))
   2:  {
   3:   ...
   4:    host.AddServiceEndpoint(typeof(ISampleService), new BasicHttpBinding(), String.Empty);
   5:    ServiceDiscoveryBehavior discoveryBehavior= new ServiceDiscoveryBehavior();             
   6:    host.Description.Behaviors.Add(discoveryBehavior);
   7:    ...       
   8:  }

Figure: Adding the service discovery behavior

The next step is to add the UdpDiscoveryEndpoint to the list of service endpoints so that our service can start listening for WS-Discovery messages.

   1:   host.AddServiceEndpoint(new UdpDiscoveryEndpoint());

Figure: Adding an UDP discovery endpoint

At this point our service is ready to receive and interpret WS-Discovery messages from the different clients on the network. However those clients are not yet aware of the existence of the service given that this one hasn’t published the Hello announcement message. We can accomplish this by simply adding a new UdpAnnoucement endpoint to the list of service endpoints.

   1:   discoveryBehavior.AnnouncementEndpoints.Add(new UdpAnnouncementEndpoint());

Figure: Adding an UDP announcement endpoint

In order to dynamically discover services using the Ad-Hoc model, a WCF client instantiates a DiscoveryClient that uses discovery endpoint specifying where to send Probe or Resolve messages. The client then calls Find that specifies search criteria within a FindCriteria instance. If matching services are found, Find returns a collection of EndpointDiscoveryMetadata. The following code illustrates that concept.

   1:   DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
   2:   FindResponse discoveryResponse= discoveryClient.Find(new FindCriteria(typeof(ISampleService)));
   3:   EndpointAddress address = discoveryResponse.Endpoints[0].Address;
   4:   
   5:  SampleServiceClient service = new SampleServiceClient(new BasicHttpBinding(), address);
   6:  service.Echo("WS-Discovery test");

Figure: WCF WS-Discovery client

The WCF implementation of the WS-Discovery Ad-Hoc model presents various aspects that I think are worth highlighting. First, WCF uses specialized discovery and announcement endpoints to process WS-Discovery messages isolating them from the service’s messages. Additionally, the use of service behaviors allow developers to incorporate the WS-Discovery capabilities as they are required without interfering with the normal service functioning. Finally, WCF clients can simply use the discovery client to dynamically resolve the service endpoint without having to make major modifications to its business logic.

We will cover the WS-Discovery managed mode in a future post.

WSO2 releases Carbon

WSO2 just announced the debut of Carbon SOA Framework which extends some of their award winning products with componentization capabilities based on the OSGI   specifications. Additionally, they have also announced the first wave of products based on the Carbon framework which includes WSO2 Web Services Application Server (WSAS) 3.0, WSO2 Enterprise Service Bus (ESB 2.0), and WSO2 Registry 2.0, as well as the new WSO2 Business Process Server (BPS).

If you haven't heard of WSO2 I encourage you to check those guys out. They have made a remarkable effort for extending some of Apache's Web Services technologies with true enterprise capabilities and I think it's fair to say that their product suite is currently the most complete SOA offering in the Java world.

I am partially skeptical about the impact OSGI will have in the way we build Service Oriented applications but I think that efforts like Carbon can only do good to a Java-SOA market that has been constantly hurt for poor performance and high complexity of some of the products provided by the major vendors.

More Posts