DCSIMG
January 2010 - Posts - berniea

berniea

January 2010 - Posts

WCF 4.0 Routing with WAS

A few days ago I wanted to build a simple message broker so I’ve decided to tryout the new routing feature WCF 4.0 is offering. When doing so I’ve encountered two problems:

The first problem was that I needed to figure out a way on how to do the routing when hosting the services under WAS (Windows Process Activation Service) since I’ve been using netTcpBinding for my services.

And the second problem was how to do XPath Message Filtering on a service operation parameters (not a Data Contract).

I’ve googled a bit on WCF 4.0 Routing but I didn’t find any samples on routing when hosting WCF in WAS. And as regards to my second problem I only came up with samples of Message Filtering only on a Data Contract.

Let’s see how to resolve this problems, to demonstrate the resolutions I’ve created a small test project called WcfTestRouting as my test case project.

First lets create the service contract and the services.

namespace WcfTestRouting
{
  [ServiceContract]
  public interface IAccountManager
  {
    [OperationContract]
    WithrawResult Withdraw(WithrawRequest request);

    [OperationContract]
    WithrawResult AnotherWithdraw(int accountId, double amount);
  }
}

As you can see I’ve added two operation contracts: one receiving a Data Contract and the other receiving just primitive method parameters.

Now I’ve added two services which I wanted to route between.

The Account Manager Service (AccountManager.svc):

namespace WcfTestRouting
{
  public class AccountManager : IAccountManager
  {
    public WithrawResult Withdraw(WithrawRequest request)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Failed,
        Remark = "Regular"
      };

      return result;
    }

    public WithrawResult AnotherWithdraw(int accountId, double amount)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Failed,
        Remark = "Regular"
      };

      return result;
    }
  }
}

And the Account Manager VIP Service (AccountManagerVip.svc):

namespace WcfTestRouting
{
  public class AccountManagerVip : IAccountManager
  {
    public WithrawResult Withdraw(WithrawRequest request)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Successful,
        Remark = "Vip"
      };

      return result;
    }

    public WithrawResult AnotherWithdraw(int accountId, double amount)
    {
      var result = new WithrawResult
      {
        Output = WithdrawalOutput.Successful,
        Remark = "Vip"
      };

      return result;
    }
  }
}

The difference between them is that the first one returns: Failed, “Regular” and the second one returns: Successful, “Vip”;

In order to deploy the services in WAS we need to do the following steps:

First, add the following service element under the services element in your web.config (you’ll need one for each service of course):

 

<service name="WcfTestRouting.AccountManager" behaviorConfiguration="GeneralBehavior">
  <host>
    <baseAddresses>
      <add baseAddress="net.tcp://localhost:808/WcfTestRouting"/>
    </baseAddresses>
  </host>
  <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
  <endpoint name="AccountManagerNetTcpEndpoint"
     address=""
     binding="netTcpBinding"
     bindingConfiguration="Tcp1Binding"
     contract="WcfTestRouting.IAccountManager" />
</service>

 

Second, configure your Web Application to allow net.tcp bindings, choose your Web Application in the IIS management console and click on the advanced settings Action:

image

You’ll receive the following window:

image

Make sure that under Behavior / Enabled Protocols, you have also a definition for “net.tcp” protocol.

Now for the routing configuration:

First let’s add the two client endpoints in the web.config file:

<client>
  <endpoint name="AccountManagerClientEndPoint"
            address="net.tcp://localhost/WcfTestRouting/AccountManager.svc"
            binding="netTcpBinding"
            bindingConfiguration="Tcp1Binding"
            contract="*" />

  <endpoint name="AccountManagerVipClientEndPoint"
            address="net.tcp://localhost/WcfTestRouting/AccountManagerVip.svc"
            binding="netTcpBinding"
            bindingConfiguration="Tcp1Binding"
            contract="*" />
</client>

Then add the routing definitions:

<routing>
      <namespaceTable>
        <add prefix="custom" namespace="http://schemas.datacontract.org/2004/07/WcfTestRouting"/>
        <add prefix="temp" namespace="http://tempuri.org/"/>
      </namespaceTable>
      <filters>
        <filter name="AccountManager_Withdraw" filterType="XPath" filterData="//custom:AccountId = 1"/>
        <filter name="AccountManagerVip_Withdraw" filterType="XPath" filterData="//custom:AccountId = 2"/>
        <filter name="AccountManager_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 1"/>
        <filter name="AccountManagerVip_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 2"/>
      </filters>
      <filterTables>
        <filterTable name="filterTable1">
          <add filterName="AccountManager_Withdraw" endpointName="AccountManagerClientEndPoint" priority="0"/>
          <add filterName="AccountManagerVip_Withdraw" endpointName="AccountManagerVipClientEndPoint" priority="0"/>
          <add filterName="AccountManager_AnotherWithdraw" endpointName="AccountManagerClientEndPoint" priority="0"/>
          <add filterName="AccountManagerVip_AnotherWithdraw" endpointName="AccountManagerVipClientEndPoint" priority="0"/>
        </filterTable>
      </filterTables>
    </routing>

After that, we need to add the routing service. Since the routing is done using System.ServiceModel.Routing.RoutingService and we are hosting the service under WAS, you need to expose it as a valid URI in your Web Application hosting the other WCF services (This can be done also in a separated Web Application). In order to do that add a blank svc file called AccountManagerRoutingService.svc to your Web Application, you can do that by simply adding a text file and changing the extension to svc.

Open the file and add the following:

 

<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService, System.ServiceModel.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"%>

This will create the route service with a valid URI you need in order to be able to do the routing in your WCF application.

Then add a service definition in your web.config as we’ve done with the previous services:

 

<service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="RoutingBehavior" >
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:808/WcfTestRouting"/>
          </baseAddresses>
        </host>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
        <endpoint name="RoutingServiceNetTcpEndpoint"
           address=""
           binding="netTcpBinding"
           bindingConfiguration="Tcp1Binding"
           contract="System.ServiceModel.Routing.IRequestReplyRouter" />
      </service>

 

Don’t forget to add the following line in the service behavior configuration:

<routing routeOnHeadersOnly="false" filterTableName="filterTable1" />

This will allow the routing to be performed also by inspecting the message body and will associate the route service it with the desired filter table.

In order to test this I’ve also written a Console Application consuming the service. Just note that when adding a Service Reference don’t add it to the Routing Service, since the routing service implements another service contract (IRequestReplyRouter) we need to add the Service Reference to one of the original services implementing our service contract (IAccountManager).

image

A resulting test would look something like this:

using (var client = new AccountManagerClient())
      {
        var request = new WithrawRequest
                        {
                          AccountId = 1,
                          Amount = 10
                        };

        var withrawResult = client.Withdraw(request);

        Console.WriteLine(withrawResult.Remark);
        Console.WriteLine(withrawResult.Output);
      }

 

In order to make this work against the Router Service we need to change the endpoint address in the clients app.conifg file. Therefore the endpoint address should be net.tcp://localhost/WcfTestRouting/AccountManagerRoutingService.svc and the client’s binding would look like this:

<client>
  <endpoint
    address="net.tcp://localhost/WcfTestRouting/AccountManagerRoutingService.svc"
    binding="netTcpBinding"
    bindingConfiguration="Tcp1Binding"
    contract="AccountManagerSrvRef.IAccountManager"
    name="RoutingServiceNetTcpEndpoint" />
</client>

Note that the client is using to the IAccountManager Service Contract.

Now when applying a different account id – the service will be routed as configured in the routing section.

The second problem was how to apply filtering when using primitive parameters in the service operation. For example when using the following operation:

public WithrawResult AnotherWithdraw(int accountId, double amount)
{
  var result = new WithrawResult
  {
    Output = WithdrawalOutput.Failed,
    Remark = "Regular"
  };

  return result;
}

The trick is very simple, lets have a look again at the filter definition in the routing section:

<namespaceTable>
  <add prefix="temp" namespace="http://tempuri.org/"/>
</namespaceTable>
<filters>
  <filter name="AccountManager_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 1"/>
  <filter name="AccountManagerVip_AnotherWithdraw" filterType="XPath" filterData="//temp:accountId = 2"/>
</filters>

As you can see I’ve added the http://tempuri.org/ to the namespaceTable and used it as the prefix for the filter data. This is due to the fact that when the Namespace is unspecified in the Service Contract it defaults to http://tempuri.org/ .