Today we face a problem in Production environment. We needed to route some WCF requests from one “publicly visible” server to an internal one. A typical routing scenario.
Fortunately these requests were received in a WCF service and this technology has a built-in routing feature since 4.0 version. To use it we don’t need to change any code, it is enough to modify the app.config/web.config of the services. Here we can see how to use it, step by step. All these XML code will be place inside system.serviceModel tag
First Step – Define the new Service
We have to define a new Routing service that will receive all the requests. It will, later, internally dispatch them depending on certain routing rules. The XML necessary is:
<services> <service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="routerConfig"> <endpoint address="" binding="basicHttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter" name="reqReplyEndpoint" /> </service> </services>
Two things that we may notice:
- The services needs a behavior configuration. In this configuration, later, we will define the routing table.
- We don’t define an address because we assume the service will be deployed in a IIS server. If we want to do some tests with Casinni, we will need to define an address.
Second Step – SVC hosting file for the new service
Since WCF 4.0 we don’t need the SVC files to host a WCF service, we can define them in app.config/web.config and the internal plumbery of WCF/IIS is smart enough to allow us calling the corresponding URL as if the SVC file really exists. This is the XML necessary for that:
<serviceHostingEnvironment> <serviceActivations> <add relativeAddress="RiskManagementServiceUAT.svc" service="System.ServiceModel.Routing.RoutingService, System.ServiceModel.Routing, Version=18.104.22.168, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </serviceActivations> </serviceHostingEnvironment>
Two more points to consider:
- The relative address must contain an extension. For example, if we define it as “RiskManagementService.UAT”, without SVC extension, it will fail.
- The service needs to specify the complete qualify name in this case, but it is not usual. In other projects that we have used this “virtual SVC” system, it was not necessary. Apparently there is some kind of limitation with the RoutingService.
Third Step – Service behavior configuration
We referenced a service behavior when we defined the service in step 1. Below we can see that configuration, that will need to indicate what routing table we have to use.
<behaviors> <serviceBehaviors> <behavior name="routerConfig"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> <routing routeOnHeadersOnly="false" filterTableName="routingTable" /> </behavior> </serviceBehaviors> </behaviors>
Things to consider here:
- Be sure the name of the behavior matches what you set when defined the service.
- serviceMetadata may not be necessary in a production environment, if you don’t expect new clients to be created from your service WSDL.
- serviceDebug MUST NOT be activated in production environment. It is a security risk.
- In the routing tag we will indicate the name for the routing table to be used in this RoutingService service.
Fourth Step – Routing table
We are getting close to the end… We need to define the routing table. There is complex patterns that we may want to follow, like taking into account headers or contents in the messages. It is really useful for versioning, load-balancing and similar stuff. However, in our concrete scenario we only want to redirect all messages to the internal server, so we didn’t need a complex solution. This was our table:
<routing> <filters> <filter name="matchAll" filterType="MatchAll" /> </filters> <filterTables> <filterTable name="routingTable"> <add filterName="matchAll" endpointName="RiskService" /> </filterTable> </filterTables> </routing>
Three important points here:
- Under filters tag we will define all possible filters. In this case we use the MatchAll filter. In bibliography there is a link for all filter types in WCF.
- Under filterTables tag we define all the possible tables. We may have different tables for different routers. In our case, we define a “routingTable” in the service behavior in Step 3 and here it is.
- As part of every table we will bound filters with endpoints. This is a very flexible approach, we can define as many possible filters as we may need for all our endpoints and later just correlate them in the filter table.
- filterName will be the name of the filter to apply.
- endpointName will be the name of the endpoint where the message will be routed when the filter is matched. This endpoint corresponds to a new element defined in the next step.
Fifth Step – Client endpoints
The router is a sum of input messages, routing logic and destination services. We have defined 2 of 3. In this point we will define what services will receive the messages once the routing is done. Here it is is the XML.
<client> <endpoint name="RiskService" address="http://appserver5/RiskManagementTool/RiskManagementService.svc" binding="basicHttpBinding" contract="*" /> </client>
So what do we have here? Just being “client” of the service where we want to send messages under certain criteria defined in Step 4. Pay attention to the name that we give to the endpoint, because this name must match the value for endpointName in the entries of the filter table defined in Step 4.
WCF is not a easy technology, but it is really powerful and, with enough knowledge and patience there is a great number of scenarios that you can cover with just defining appropriate XML configuration. No coding, no compiling, no deploying, just playing with the app.config/web.config and you get a very powerful Routing Service that is able to route base on headers, message content, protocol, etc.