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:
- Create a blank Visual Studio 2010 solution named ContentBasedRouting.
- Add a Class Library project called CustomerContract.
- Rename the Class1.cs file to ICustomer.cs.
- Put the following code in it:
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 DataMember
s as automatic properties.
- Next, we add references to System.ServiceModel.dll, System.ServiceModel.Description.dll, System.Runtime.Serialization.dll, and System.ServiceModel.Web.dll.
- 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.
- Rename the SVC file to PremiumCustomerService.svc. It should contain the following code:
<%@ ServiceHost Language="C#" Debug="true"
Service="PremiumCustomerService.PremiumCustomerService"
CodeBehind="PremiumCustomerService.svc.cs" %>
- Go to the code-behind file and add the following code:
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
{
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()
.
- Next, the web.config file has to be modified. The following should be the contents of the web.config file:
="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>
<serviceMetadata httpGetEnabled="true"/>
<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.
- Now add another WCF service application named OrdinaryCustomerService to the solution.
- Rename the .svc file to OrdinaryCustomerService.svc. The contents of the file should be the following:
<%@ ServiceHost Language="C#" Debug="true"
Service="OrdinaryCustomerService.OrdinaryCustomerService"
CodeBehind="OrdinaryCustomerService.svc.cs" %>
- Modify the code-behind to contain the following code:
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
{
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()
.
- Next, the corresponding web.config file should be modified to contain the following:
="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>
<serviceMetadata httpGetEnabled="true"/>
<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.
- Add another WCF service application called CustomerService to the solution. Now this is where we would define our routing service, filters, and XPath expressions.
- Add a reference to System.ServiceModel.Routing.dll.
- The contents of CustomerService.svc should be the following:
<%@ 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.
- 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:
="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">
<serviceMetadata httpGetEnabled="true"/>
<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.
- Now we can test the service in IIS Manager to see that the URL is http://localhost/CustomerService/CustomerService.svc.
- 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.
- The form's code-behind file ProtocolBridgingForm.cs should have the following code:
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.