Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Content Based Routing Using WCF 4

4.74/5 (13 votes)
28 Jul 2010CPOL5 min read 75.1K   899  
This article explains how to do content based routing using the WCF 4 RoutingService.

Introduction

This article describes content based routing using WCF. Content based routing fulfills one of the four tenets of SOA which states that schemas and contracts can be shared, not classes. In message oriented applications, the client needs to only bother about the message it needs to submit and one service which would take care of its routing, i.e., submitting the message to one of the appropriate downstream WCF services. Thus, to the outside world, only the routing services and the contracts are exposed. This alleviates the need of point to point communication, and makes WCF behave more or less like a service bus.

Background

A background knowledge of C# 4.0, WCF 4.0, and ASP.NET are required to comprehend the article properly. A basic theoretical understanding of SOA and ESB are desirable.

Using the Code

The following steps are to be completed in order to realize content based routing using WCF 4.0:

  1. Create a blank Visual Studio 2010 solution named ContentBasedRouting.
  2. Add a Class Library project called CustomerContract.
  3. Rename the Class1.cs file to ICustomer.cs.
  4. Put the following code in it:
  5. C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Web;
    using System.Runtime.Serialization;
    
    namespace CustomerContract
    {
        [ServiceContract]
        public interface ICustomer
        {
            [OperationContract]
            string GetCustomerDetails(Customer cust);
        }
        [DataContract]
        public class Customer
        {
            [DataMember]
            public string CustomerID { get; set; }
            [DataMember]
            public string CustomerName { get; set; }
            [DataMember]
            public string CustomerCreditRating { get; set; }
        }
    }

    Here, we declare a ServiceContract, an interface ICustomer with the OperationContract as GetCustomerDetails taking a Customer object as parameter. We declare a DataContract, and a class Customer with three DataMembers as automatic properties.

  6. Next, we add references to System.ServiceModel.dll, System.ServiceModel.Description.dll, System.Runtime.Serialization.dll, and System.ServiceModel.Web.dll.
  7. Add a WCF service application called PremiumCustomerService to the solution. Also create a virtual directory in IIS 7.0 with physical path pointing to the PremiumCustomerService folder.
  8. Rename the SVC file to PremiumCustomerService.svc. It should contain the following code:
  9. ASP.NET
    <%@ ServiceHost Language="C#" Debug="true" 
        Service="PremiumCustomerService.PremiumCustomerService" 
        CodeBehind="PremiumCustomerService.svc.cs" %>
  10. Go to the code-behind file and add the following code:
  11. C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace PremiumCustomerService
    {
        // NOTE: You can use the "Rename" command on the "Refactor" menu
        // to change the class name "Service1"
        // in code, svc and config file together.
        public class PremiumCustomerService : CustomerContract.ICustomer
        {
            public string GetCustomerDetails(CustomerContract.Customer cust)
            {
                return "Customer ID = " + cust.CustomerID + 
                       " Premium CustomerName = " + 
                       cust.CustomerName + " CustomerCreditRating = " + 
                       cust.CustomerCreditRating;
            }
        }
    }

    Here we add a class PremiumCustomerService which implements the interface ICustomer, subsequently implementing the method GetCustomerDetails().

  12. Next, the web.config file has to be modified. The following should be the contents of the web.config file:
  13. XML
    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <netTcpBinding>
            <binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
              <security mode="None" />
            </binding>
          </netTcpBinding>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_ICustomer">
              <security mode="None"/>
            </binding>
          </basicHttpBinding>
        </bindings>
        <services>
          <service name="PremiumCustomerService">
            <endpoint address="PremiumCustomerService.svc" binding="basicHttpBinding" 
               bindingConfiguration="BasicHttpBinding_ICustomer" 
               contract="CustomerContract.ICustomer"/>
            <endpoint address="mex" binding="mexHttpBinding" 
               contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost/PremiumCustomerService/" />
              </baseAddresses>
            </host>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <!-- To avoid disclosing metadata information, set the value below 
                 to false and remove the metadata endpoint above before deployment -->
              <serviceMetadata httpGetEnabled="true"/>
              <!-- To receive exception details in faults for debugging purposes, 
                 set the value below to true. Set to false before deployment 
                 to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
     <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
     </system.webServer> 
    </configuration>

    Here we have PremiumCustomerService defined with basicHttpBinding. Now the service is ready. At this point, compile and build the solution. Now we can browse the service from IIS manager and see the URL as http://localhost/PremiumCustomerService/PremiumCustomerService.svc.

  14. Now add another WCF service application named OrdinaryCustomerService to the solution.
  15. Rename the .svc file to OrdinaryCustomerService.svc. The contents of the file should be the following:
  16. ASP.NET
    <%@ ServiceHost Language="C#" Debug="true" 
        Service="OrdinaryCustomerService.OrdinaryCustomerService" 
        CodeBehind="OrdinaryCustomerService.svc.cs" %>
  17. Modify the code-behind to contain the following code:
  18. C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace OrdinaryCustomerService
    {
        // NOTE: You can use the "Rename" command on the "Refactor"
        // menu to change the class name "Service1"
        // in code, svc and config file together.
        public class OrdinaryCustomerService : CustomerContract.ICustomer
        {
            public string GetCustomerDetails(CustomerContract.Customer cust)
            {
                return "Customer ID = " + cust.CustomerID + 
                       " Ordinary CustomerName = " + 
                       cust.CustomerName + " CustomerCreditRating = " + 
                       cust.CustomerCreditRating;
            }
        }
    }

    Here we add a class called OrdinaryCustomerService which implements the ICustomer interface and hence the method GetCustomerDetails().

  19. Next, the corresponding web.config file should be modified to contain the following:
  20. XML
    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <netTcpBinding>
            <binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
              <security mode="None" />
            </binding>
          </netTcpBinding>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_ICustomer">
              <security mode="None"/>
            </binding>
          </basicHttpBinding>
        </bindings>
        <services>
          <service name="OrdinaryCustomerService">
            <endpoint address="OrdinaryCustomerService.svc" binding="basicHttpBinding" 
               bindingConfiguration="BasicHttpBinding_ICustomer" 
               contract="CustomerContract.ICustomer"/>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost/OrdinaryCustomerService/" />
              </baseAddresses>
            </host>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <!-- To avoid disclosing metadata information, set the value below 
                  to false and remove the metadata endpoint above before deployment -->
              <serviceMetadata httpGetEnabled="true"/>
              <!-- To receive exception details in faults for debugging purposes, 
                  set the value below to true. Set to false before deployment 
                  to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
     <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
     </system.webServer>
    </configuration>

    We have completed another service called OrdinaryCustomerService. At this point, compile and build the solution again. In IIS Manager, we can browse the service to see that the URL is http://localhost/OrdinaryCustomerService/OrdinaryCustomerService.svc.

  21. Add another WCF service application called CustomerService to the solution. Now this is where we would define our routing service, filters, and XPath expressions.
  22. Add a reference to System.ServiceModel.Routing.dll.
  23. The contents of CustomerService.svc should be the following:
  24. ASP.NET
    <%@ ServiceHost Language="C#" Debug="true" 
        Service="System.ServiceModel.Routing.RoutingService,System.ServiceModel.Routing, 
                 version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>

    Here we are using the System.ServiceModel.Routing.RoutingService class as the service. This class is predefined in the System.ServiceModel.Routing.dll assembly. We will compile and build the solution again.

  25. Now we present the most important thing, i.e., the web.config file for the routing service. Here we define a filterTable for the service behavior and add filters and XPath expressions under the routing element. The services are defined under the client section of web.config. The contents of web.config should be like the following:
  26. XML
    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <netTcpBinding>
            <binding name="NetTcpBinding_ICustomer" portSharingEnabled="true">
              <security mode="None" />
            </binding>
          </netTcpBinding>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_ICustomer">
              <security mode="None"/>
            </binding>
          </basicHttpBinding>
        </bindings>
        <client>
          <endpoint 
            address="http://localhost/PremiumCustomerService/PremiumCustomerService.svc" 
            binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICustomer" 
            contract="*" name="CustomerServiceLibrary_PremiumCustomerService"/>
          <endpoint 
            address="http://localhost/OrdinaryCustomerService/OrdinaryCustomerService.svc" 
            binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICustomer" 
            contract="*" name="CustomerServiceLibrary_OrdinaryCustomerService"/>
          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </client>
        <services>
          <service behaviorConfiguration="RoutingServiceBehavior" 
                   name="System.ServiceModel.Routing.RoutingService">
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost/CustomerService/"/>
              </baseAddresses>
            </host>
            <endpoint address="" binding="basicHttpBinding" 
               bindingConfiguration="BasicHttpBinding_ICustomer" 
               contract="System.ServiceModel.Routing.IRequestReplyRouter" 
               name="RoutingServiceEndpoint">
            </endpoint>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="RoutingServiceBehavior">
              <!-- To avoid disclosing metadata information, set the value below 
                 to false and remove the metadata endpoint above before deployment -->
              <serviceMetadata httpGetEnabled="true"/>
              <!-- To receive exception details in faults for debugging purposes, 
                 set the value below to true. Set to false before deployment 
                 to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="true"/>
              <routing  filterTableName="routingRules" routeOnHeadersOnly="False"/>
            </behavior>
            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <routing>
          <namespaceTable>
            <add prefix="cc" 
              namespace="http://schemas.datacontract.org/2004/07/CustomerContract" />
          </namespaceTable>
          <filters>
            <filter name="PremiumCustomerFilter" filterType="XPath" 
                 filterData="//cc:CustomerCreditRating = 'Good'"/>
            <filter name="OrdinaryCustomerFilter" 
                 filterType="XPath" filterData="//cc:CustomerCreditRating = 'Bad'"/>
          </filters>
          <filterTables>
            <filterTable name="routingRules">
              <add filterName="PremiumCustomerFilter" 
                 endpointName="CustomerServiceLibrary_PremiumCustomerService" 
                 priority="0"/>
              <add filterName="OrdinaryCustomerFilter" 
                 endpointName="CustomerServiceLibrary_OrdinaryCustomerService" 
                 priority="0"/>
            </filterTable>
          </filterTables>
          <backupLists>
            <backupList name="CustomerBackupList">
              <add endpointName="CustomerServiceLibrary_OrdinaryCustomerService"/>
            </backupList>
          </backupLists>
        </routing>
      </system.serviceModel>
     <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
      </system.webServer>
    </configuration>

    Here we see that we have implemented routing using filter tables, filters, and XPath expressions. The filter tables implement routing rules which presents the corresponding endpoints for the filters which are evaluated using the XPath expression. The contents of the CustomerCreditRating public property of the Customer class is used to determine the endpoint for the downstream service to which the message would be forwarded subsequently.

  27. Now we can test the service in IIS Manager to see that the URL is http://localhost/CustomerService/CustomerService.svc.
  28. Next we add a Windows Forms application called ProtocolBridgingClient to the solution and add a service reference corresponding to the routing service: http://localhost/CustomerService/CustomerService.svc. We also add a reference to System.ServiceModel.dll.
  29. The form's code-behind file ProtocolBridgingForm.cs should have the following code:
  30. C#
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    
    namespace ProtocolBridgingClient
    {
        public partial class ProtocolBridgingForm : Form
        {
            public ProtocolBridgingForm()
            {
                InitializeComponent();
            }
    
            private void ProtocolBridgingForm_Load(object sender, EventArgs e)
            {
                try
                {
                    BasicHttpBinding binding = new BasicHttpBinding();
                    EndpointAddress address = new EndpointAddress(
                       "http://localhost/CustomerService/CustomerService.svc");
                    CustomerContract.ICustomer proxy = 
                       ChannelFactory<CustomerContract.ICustomer>.CreateChannel(
                       binding, address);
                    CustomerContract.Customer cust1 = 
                       new CustomerContract.Customer { CustomerID = "ar0045855", 
                       CustomerName = "Ambar Ray", CustomerCreditRating = "Good" };
                    CustomerContract.Customer cust2 = 
                       new CustomerContract.Customer { CustomerID = "am0046067", 
                       CustomerName = "Abhijit Mahato", CustomerCreditRating = "Bad" };
                    string res1 = proxy.GetCustomerDetails(cust1);
                    string res2 = proxy.GetCustomerDetails(cust2);
                    txtCustomer.Text = res1 + "\n" + res2;
                }
                catch (Exception ex)
                {
                    txtCustomer.Text = (ex.InnerException != null) ?  
                       ex.InnerException.Message + "\t" + ex.Message : ex.Message;
                }
            }
    
            private void ProtocolBridgingForm_FormClosed(object sender, 
                                                         FormClosedEventArgs e)
            {
                GC.Collect();
            }
    
        }
    }

    In the form load event, we first create a BasicHttpBinding object and then an EndpointAddress object specifying the URL of the routing service. Then, we create the proxy object by using the ChannelFactory.CreateChannel() method. We create the Customer objects, with one having 'Good' CustomerCreditRating and another having 'Bad' CustomerCreditRating, such that the former finally gets routed to the premium customer service and the latter gets routed to the ordinary customer service. We then call the GetCustomerDetails method and collect the output in the rich text box.

Points of Interest

This could be the basis for creating an ESB (Enterprise Service Bus) using WCF. We can have one router service for each schema / contract and corresponding downstream services operating on the respective schema / contract. This way, just submitting the message to the appropriate routing service helps in determining the message's 'itinerary' in the downstream systems depending on the contents of the message. The mapping of messages could be implemented using readily available open source code. Thus, a lightweight ESB could be created having routing along with transformation capabilities.

License

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