In the last post I introduced you to some of the design concepts around content based routing, and talked about our implementation through WCF's MessageFilterTable. In this post, I'll give you some examples of what configuring CBR at the Routing Service looks like.
Last time, I said that we'd implemented content based routing through MessageFilters and MessageFilterTable. But what does this look like? Well here's an example of what the configuration of a Routing Service message filter table could look like:
<!-- use the namespace table element to define a prefix for our custom namespace-->
<add prefix="custom" namespace="http://my.custom.namespace/"/>
<!--define the different message filters-->
<!--define an xpath message filter to look for the custom header coming from the client-->
<filter name="XPathFilter" filterType="XPath" filterData="sm:header()/custom:MyHeader = 1"/>
<!--define an endpoint name filter looking for messages that show up on the endpoint named serviceEndpoint1-->
<filter name="EndpointNameFilter" filterType="EndpointName" filterData="serviceEndpoint1"/>
<!--define a filter looking for messages that show up with this address prefix. Assume this is distinct from ServiceEndpoint1's address -->
<filter name="PrefixAddressFilter" filterType="PrefixEndpointAddress" filterData="http://localhost/routingservice/serviceEndpoint2/"/>
<!--Set up the custom message filters. In this example, we'll use the example round robin message filter, which alternates between the references-->
<filter name="CustomFilter1" filterType="Custom" customType="MyFullNamespace.MyType, MyAssembly" filterData="customFilterData"/>
<!--add the filters to the message filter table-->
<!--first look for the custom header, and if we find it, send the message to destination endpoint 1-->
<add filterName="XPathFilter" endpointName="destinationEndpoint1" priority="2"/>
<!--if the header wasn't there, send the message based on which endpoint it arrived at-->
<!--we determine this through the endpoint name, or through the address prefix-->
<add filterName="EndpointNameFilter" endpointName="destinationEndpoint1" priority="1"/>
<add filterName="PrefixAddressFilter" endpointName="destinationEndpoint2" priority="1"/>
<!--if none of the other filters have matched, this message show up at some other endpoint -->
<!--and without the custom header. In this case, perform some custom logic to determine if it should be sent -->
<!--to destination 2 or rejected -->
<add filterName="CustomFilter1" endpointName="destinationEndpoint2" priority="0"/>
There! So what's going on here? Well, hopefully the comments give you some idea, but let's walk through it:
In the <filters> section, we define and configure the different MessageFilters that we want our Routing Service to be able to use. We do this by defining (usually) three things:
The filter Name: This is the value that we'll use to reference the filter when we add it to the messageFilterTable later on
The filter Type: This is the type of the filter that we want to create, such as an Action or XPath filter.
The filter Data: This is the data that will be fed to the MessageFilter's constructor when it is created.
Here are all the different filter types and an example of each:
Filter Data Meaning
The Action to filter upon.
<filter name="action1" filterType="Action" filterData="http://namespace/contract/operation" />
The address to filter upon (in the To header).
<filter name="address1" filterType="EndpointAddress" filterData="http://host/vdir/s.svc/b" />
The address to filter upon using longest prefix matching.
<filter name="prefix1" filterType="EndpointAddressPrefix" filterData="http://host/" />
filterData is not used, instead filter1 and filter2 have the names of the corresponding messages filters (also in the table), which should be ANDed together.
<filter name="and1" filterType="And" filter1="address1" filter2="action1" />
customType attribute is the fully-qualified type name of the class to create, filterData is the string to pass to the constructor when creating the filter.
<filter name="custom1" filterType="Custom" customType="CustomAssembly.CustomMsgFilter, CustomAssembly" filterData="Custom Data" />
The name of the service endpoint, for example: “serviceEndpoint1”. This should be one of the endpoints exposed on the Routing Service.
<filter name="stock1" filterType="Endpoint" filterData="SvcEndpoint" />
filterData is not used. This filter will always match all message.
<filter name="matchAll1" filterType="MatchAll" />
The XPath query to use when matching messages. Note that if the data you need to access is inside the message body, you will need to change the Routing Service to allow this by setting RouteOnHeadersOnly on either the RoutingBehavior (config) or RoutingConfiguration (code) to FALSE
<filter name="XPath1" filterType="XPath" filterData="//ns:element" />
Note that the MatchAll and AND message filters are a bit anomalous in that they don't match the normal pattern of name, type, data. The MatchAll is a very simplistic filter in that it will match any message (return true) regardless of the input. For this reason it needs no configuration. The AND message filter also doesn't require filterData, however it does have two other fields, filter1 and filter2. In order to configure the AND message filter, set these fields to the names of other filters you've already defined. The AND filter will (expectedly) only return true if both of the filters return true. Note that this is a non-short-circuiting AND, so even if the first filter returns false, the second filter will be interrogated.
Now that we've defined the filters, we need to add them to a message filter table, so that the routing service can use them. We do this by making entries into the <filterTables> section, first to define a filter table (which we reference when creating a RoutingBehavior (or a RoutingConfiguration if we're configuring the Routing Service programatically), and then to add in the filter definitions we've created. When adding a filter, we need to define several things:
- The FilterName: this is the name of the filter that we want to use. Messages matching this filter will be sent to...
- The EndpointName: this is the name of the client endpoint (defined elsewhere in config) that we would like to send messages to
- (Optional) Priority: Filter priorities define the order in which filters are executed. 0 is the default priority, and filters are executed sooner the higher their priority is. Thus, in the configuration above, the XPath filter is always executed first, and the custom filter is always executed last. Filters with lower numbers are only executed if no filter with a higher number matched. I will delve into the impact that filters can have on your routing rules later, but in general you should avoid defining them if possible.
- (Optional, not pictured) backupList: A backup list is a list of endpoints that you would like the Routing Service to use in case the primary endpoint can't be reached. I'll talk a lot more about backupLists and how to define them when we talk about the Routing Service's error handling capabilities.
There you have it! Now you have all of the pieces and parts defined and can get your own Routing Service set up to examine and route messages based on their content.