SIMPLER Way of Hosting the WCF 4.0 Routing Service in IIS7

Posted on March 9, 2010

15


A few months back I was screwing around with the WCF Routing Service and trying something besides the “Hello World” demos that always used self-hosted versions of this new .NET 4.0 WCF capability. In my earlier post, I showed how to get the Routing Service hosted in IIS.  However, I did it in a round-about way since that was only way I could get it working.  Well, I have since learned how to do this the EASY way, and figured that I’d share that.

As a quick refresher, the WCF Routing Service is a new feature that provides a very simple front end service broker which accepts inbound messages and distributes them to particular endpoints based on specific filter criteria.  It leverages your standard content-based routing pattern, and is not a pub/sub mechanism.  Rather, it should be used when you want to send an inbound message to one of many possible destination endpoints.

I’ll walk through a full solution scenario here.  We start with a standard WCF contract that will be shared across the services sitting behind the Router service.  Now you don’t HAVE to use the same contract for your services, but if not, you’ll need to transform the content into the format expected by each downstream service, or, simply accept untyped content into the service.  Your choice.  For this scenario, I’m using the Routing Service to accept ticket orders, and based on the type of event that the ticket applies to, routes it to the right ticket reservation system.  My common contract looks like this:

[ServiceContract]
    public interface ITicket
    {
        [OperationContract]
        string BuyTicket(TicketOrder order);
    }

    [DataContract]
    public class TicketOrder
    {
        [DataMember]
        public string EventId { get; set; }
        [DataMember]
        public string EventType { get; set; }
        [DataMember]
        public int CustomerId { get; set; }
        [DataMember]
        public string PaymentMethod { get; set; }
        [DataMember]
        public int Quantity { get; set; }
        [DataMember]
        public decimal Discount { get; set; }
    }

I then added two WCF Service web projects to my solution.  They each reference the library holding the previously defined contract, and implement the logic associated with their particular ticket type.  Nothing earth-rattling here:

public string BuyTicket(TicketOrder order)
    {
        return "Sports - " + System.Guid.NewGuid().ToString();
    }

I did not touch the web.config files of either service and am leveraging the WCF 4.0 capability to have simplified configuration. This means that if you don’t add anything to your web.config, some default behaviors and bindings are used.

I then deployed each service to my IIS 7 environment and tested each one using the handy WCF Test Client tool.  As I would hope for, calling my service yields the expected result:

2010.3.9router01

Ok, so now I have two distinct services which add orders for a particular type of event.  Now, I want to expose a single external endpoint by which systems can place orders.  I don’t want my service consumers to have to know my back end order processing system URLs, and would rather they have a single abstract endpoint which acts as a broker and routes messages around to their appropriate target.

So, I created a new WCF Service web application.  At this point, just for reference I have four projects in my solution.

2010.3.9router02

Alrighty then.  First off, I removed the interface and service implementation files that automatically get added as part of this project type.  We don’t need them.  We are going to reference the existing service type (Routing Service) provided by WCF 4.0.  Next, I went into the .svc file and changed the directive to point to the FULLY QUALIFIED path of the Routing Service.  I didn’t capitalize those words in the last sentence just because I wanted to be annoying, but rather, because this is what threw me off when I first tried this back in December.

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

Now all that’s left is the web.config file.  The configuration file needs a reference to our service, a particular behavior, and the Router specific settings.

I first added my client endpoints:

<client>
			<endpoint address="http://localhost/Seroter.Blog.WcfRoutingDemo.SportsTicketService/SportsTicketService.svc" binding="basicHttpBinding" bindingConfiguration="" contract="*" name="SportsTickets"/>
			<endpoint address="http://localhost/Seroter.Blog.WcfRoutingDemo.ConcertTicketService/ConcertTicketService.svc" binding="basicHttpBinding" bindingConfiguration="" contract="*" name="ConcertTickets"/>
		</client>

Then I added the new “routing” configuration section.  Here I created a namespace alias and then set each Xpath filter based on the “EventType” node in the inbound message.  Finally, I linked the filter to the appropriate endpoint that will be called based on a matched filter.

<routing>
      <namespaceTable>
        <add prefix="custom" namespace="http://schemas.datacontract.org/2004/07/Seroter.Blog.WcfRoutingDemo.Contracts"/>
      </namespaceTable>
      <filters>
        <filter name="SportsFilter" filterType="XPath" filterData="//custom:EventType = 'Sports'"/>
        <filter name="ConcertFilter" filterType="XPath" filterData="//custom:EventType = 'Concert'"/>
      </filters>
      <filterTables>
        <filterTable name="filterTable1">
          <add filterName="SportsFilter" endpointName="SportsTickets" priority="0"/>
          <add filterName="ConcertFilter" endpointName="ConcertTickets" priority="0"/>
        </filterTable>
      </filterTables>
    </routing>

After that, I added a new WCF behavior which leverages the “routing” behavior and points to our new filter table.

<behavior name="RoutingBehavior">
          <routing routeOnHeadersOnly="false" filterTableName="filterTable1" />
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>

Finally, I’ve got my service entry which uses the above behavior and defines which contract we wish to use.  In my case, I have request/reply operations, so I leveraged the corresponding contract in the Routing service.

<services>
       <service behaviorConfiguration="RoutingBehavior" name="System.ServiceModel.Routing.RoutingService">
          <endpoint address="" binding="basicHttpBinding" bindingConfiguration=""
             name="RouterEndpoint1" contract="System.ServiceModel.Routing.IRequestReplyRouter" />
       </service>
    </services>

After deploying the routing service project to IIS, we’re ready to test.  What’s the easiest way to test this bad boy?  Well, we can take our previous WCF Test Client entry, and edit it’s WCF configuration.  This way, we get the strong typing on the data entry, but ACTUALLY point to the Routing service URL.

2010.3.9router03

After the change is made, we can view the Configuration file associated with the WCF Test Client and see that our endpoint now refers to the Routing service.

2010.3.9router04

Coolio.  Now, we can test.  So I invoked the BuyTickets operation and first entered a “Sports” type ticket.

2010.3.9router05

Then, ALL I did was switch the EventType from “Sports” to “Concert” and the Routing service should now call the service which fronts the concert reservation service.

2010.3.9router06

There you have it.  What’s nice here, is that if I added a new type of ticket to order, I could simply add a new back end service, update my Routing service filter table, and my service consumers don’t have to make a single change.  Ah, the power of loose coupling.

You all put up with these types of posts from me even though I almost never share my source code.  Well, your patience has paid off.  You can grab the full source of the project here.  Knock yourselves out.

Share

About these ads