The reason I saw this behavior is due to the way the WCF adapter manages exceptions. i.e. it doesn’t send them through the receive pipeline so any remaining steps in the itinerary don’t execute. See the 101 section above.
Solving the problem
I found three ways to resolve the problem. Simply put, these are:
- Go back to using orchestrations
- Use a hybrid solution of itineraries and orchestrations
- Dive into the WCF stack and force the WCF adapter to do what I want it to do.
I didn’t like the first option. Primarily because I have found it to be a laborious and time-consuming exercise deploying orchestration based services to different environments. There are frameworks that help but it’s a lot more work than deploying an itinerary based solution. (a subject for another day).
The second option… well, isn’t this just adding more complexity to a solution that I have been trying to avoid. i.e. orchestration based services.
The final option is exactly what I was looking for. Without orchestrations, my services can respond quicker and have lower turnaround times. This is because, without orchestrations, I can eliminate a number of round trips to the MessageBox, which will shave a few 100 milliseconds from the response times of my service. Even more attractive because speed is important to my customer.
The Solution – WCF Channel Wrapper
As it turns out, you can make the WCF adapter do what I want it to and it didn’t take too big a stick to beat it into submission. The solution is quite simple to achieve and is far more flexible than the previous two I mention. (I did play with the second option and will provide details another day – part II)
My solution is to wrap the default WCF channel with my own channel in which I can trap exceptions and create error messages.
I’m not going to dive into the WCF stack here. All you need to know is that when consuming a WCF service, the messages pass through a series of levels or layers in a not too dissimilar way that the BizTalk ports work. You can hook you own components (extensions, handlers, etc) into each layer and you can even replace a layer with your own. Search the web for “WCF stack” if you want to know more.
So, after much research and fighting with configuration settings I established a way of wrapping the default WCF Channel with my own channel. With this in place, I was able to do exactly what I wanted to do. My channel wrapper uses the default channel to invoke the service but also allows me to trap exceptions. I can translate errors into messages and the WCF adapter, totally ignorant of the fact, just passes the message to the receive pipeline as if it were a normal message.
This is how it works…
Step 1 – Create the Channel Wrapper
F.Y.I. For my example I’m using BizTalk 2009, Visual Studio 2008 and the ESB Toolkit 2.0.
So, here are some of the details for creating a channel wrapper. I have a plain old assembly that contains the following types.
This looks quite complicated but the parts we need are not. I won’t go into the details of the Async, base or utility classes. You can look at these in the sample.
The solution all starts with the ServiceBindingExtensionElement class. This is a configuration element that we will be referencing when we setup our send port. In turn, the Binding Extension Element provides an instance of the ServiceBindingElement class. The Binding Element is responsible for providing both the Service Request and Reply Channel factories. The factories are responsible for creating channels for us.
My factory classes create custom channels that wrap the default channels. This is how my Request Channel factory builds the custom Request Channel that wraps the default channel:
protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)
{
TChannel innerChannel = _innerChannelFactory.CreateChannel(to, via);
if (typeof(IRequestChannel) == typeof(TChannel))
{
return (TChannel)(object)(new ServiceRequestChannel(this,
(IRequestChannel)innerChannel,
_maxBufferSize,
_messageVersion));
}
throw new InvalidOperationException();
}
The real work takes place in my custom ServiceRequestChannel. This uses the default channel to invoke the service. It does this in a try … catch block so that we can grab the exceptions when they occur. This is what the guts look like:
MessageBuffer messageBuffer = message.CreateBufferedCopy(int.MaxValue);
try
{
Message messageToSend = messageBuffer.CreateMessage();
reply = InnerChannel.Request(messageToSend);
string messageString = reply.ToString();
if (messageString.ToLower().Contains("fault"))
{
string action = string.Empty;
if (message != null)
action = message.Headers.Action;
string faultstring = "An error occured processing the service request.";
XmlDocument xmlDocument = new XmlDocument();
XmlNode detailXmlNode = null;
try
{
xmlDocument = Utilities.GetXmlDocument(messageString);
faultstring = Utilities.GetXPathValue(xmlDocument, "//*[local-name()='faultstring']");
detailXmlNode = Utilities.GetXPathXmlNode(xmlDocument, "//*[local-name()='detail']");
}
catch { }
throw new SoapException(faultstring,
SoapException.ClientFaultCode,
action,
detailXmlNode);
}
}
catch (SoapException exception)
{
reply = CreateResponseMessage(messageBuffer.CreateMessage(),
"An error occured comunicating with a sub-system.",
exception);
}
catch (CommunicationException exception)
{
reply = CreateResponseMessage(messageBuffer.CreateMessage(),
"An general error occured comunicating with a sub-system.",
exception);
}
catch (TimeoutException exception)
{
reply = CreateResponseMessage(messageBuffer.CreateMessage(),
"The operation timed out while communicating with a sub-system.",
exception);
}
catch (Exception exception)
reply = CreateResponseMessage(messageBuffer.CreateMessage(),
"An unexpected error occured",
exception);
}
finally { }
My ServiceRequestChannel class has a method called CreateResponseMessage that it creates a response
message for us. This is what it looks like.
private Message CreateResponseMessage(Message message, string errorMessage, Exception exception)
{
string action = null;
if (message != null)
action = message.Headers.Action;
Message replyMessage = Message.CreateMessage(MessageVersion.Default,
"*",
new ResponseBodyWriter("soap:Client",
exception.Message,
action,
errorMessage,
exception));
if (message != null)
{
if (message.Properties != null &&
message.Properties.Count > 0)
{
replyMessage.Properties.CopyProperties(message.Properties);
}
}
replyMessage.Headers.MessageId = new UniqueId();
return replyMessage;
}
As you see we are using a custom BodyWriter called ResponseBodyWriter. This is where I compose an XML body that will be converted into a message. My Body Writer creates a SOAP Fault that can be processed by a map in my itinerary. Here is an example of a reply message from my Body Writer. The detail section contains a information about the exception and all inner exceptions.
<SOAP:Envelope SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<SOAP:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>The request channel timed out attempting to send after 00:00:10. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout.</faultstring>
<faultactor>http://namespace.org/AnAction</faultactor>
<detail>
<ErrorMessage>The operation timed out while communicating with a sub-system.</ErrorMessage>
<Exception>
<Message>The request channel timed out attempting to send after 00:00:10. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout.</Message>
<Type>System.TimeoutException</Type>
<Exception>
<Message>The HTTP request to 'http://nowhere/nowhere.svc' has exceeded the allotted timeout of 00:00:00. The time allotted to this operation may have been a portion of a longer timeout.</Message>
<Type>System.TimeoutException</Type>
</Exception>
</Exception>
</detail>
</SOAP:Fault>
</SOAP:Body>
</SOAP:Envelope>
Step 2 – Deploying The Channel Wrapper
To deploy, just compile the assembly and add it to the GAC. Then, to make the new channel available to your service, open the machine.config file and add a new Binding Extension like this.
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="ESBExceptionHandlingChannel" type="ESB.ExceptionHandling.ServiceModel.Configuration.ServiceBindingExtensionElement, ESB.ExceptionHandling.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a496c9267b296339" />
</bindingElementExtensions >
</extensions>
</system.serviceModel>
Only adding this to the BizTalk config file means that it won’t be accessible to IIS.
Remember to add this to both the 32bit and 64bit machine.config files if you are running a 64bit OS.
Also, remember to restart IIS and all BizTalk hosts to pick up the config file changes.
Step 3 – Creating your BizTalk Application
The next step is to create a BizTalk application called ESB.ExceptionHandling. We will use this when creating our itinerary. It references the Microsoft.Practices.ESB application so we can choose the ESB pipelines when configuring our ports.
Our application will have all our schema, maps and ports. My example contains a two schema. One for request messages and another for responses. The application also contains a map that will be translating the SOAP Fault into a message conforming to our response schema. This map has a source message type of BTS.soap_envelope_1__1 from the Microsfot.BizTalk.GlobalPropertySchema reference.
My port configuration includes a WCF receive port called TestReceivePort. It has been setup in the same way as described by the ESB Toolkit tutorials. i.e. it has the required filters and uses the ItinerarySelectReceiveXml pipeline with the following settings:
Property Name | Value |
ItineraryFactKey | Resolver.Itinerary |
ResolverConnectionString | ITINERARY:\\name=ESB.ExceptionHandling.Test |
I expose the receive port via IIS by using the WCF Publishing wizard. I have used WCF-BasicHttp and have published the schema because there isn’t an orchestration to publish. I can now use soapUI to consume my service.
The send port is configured as a dynamic port using the ItinerarySendPassthrough and ItineraryForwarderSendReceive pipelines with default settings.
It’s all pretty much standard ESB service configuration.
Step 4 – Building your itinerary
Now we are ready to build our itinerary. Here is what mine looks like. It’s basically a pass-through service with a transform after the off-ramp. The transform will convert the SOAP Fault message we get from the adapter into our response type.
The off-ramp is configured to consume a WCF service in the “Route to WCF Service” resolver found under the “Route Message” service. It needs to have a WCF-Custom transport type so that we can add our channel wrapper into the mix. Here are the settings I have used. You can use the BRE as I have for my customer but for this example I’m going static
Property | Value |
Resolver Implementation | Static Resolver Extension |
Message-Exchange Pattern | Two-Way |
Transport Name | WCF-Custom |
Transport Location | http://nowhere/nowhere.svc |
Action | AnAction |
Target Namespace | http://namespace.org |
Endpoint Configuration | BindingConfiguration=<binding name="customBinding" closeTimeout="00:00:10" openTimeout="00:00:10" sendTimeout="00:00:10"><ESBExceptionHandlingChannel /><httpTransport /></binding>&BindingType=customBinding&PropagateFaultMessage=true |
The interesting property here is the Endpoint Configuration. It needs to be set to customBinding and I enable the PropagateFaultMessage option but you don’t need to.
It’s in the custom binding where we add our channel wrapper and transport type. Use the extension name we added to the machine.config file (i.e. ESBExceptionHandlingChannel) to active our wrapper. So, this is what we have.
<binding name="customBinding" closeTimeout="00:00:10" openTimeout="00:00:10" sendTimeout="00:00:10">
<ESBExceptionHandlingChannel />
<httpTransport />
</binding>
Note: I have set the timeout values very low to so I don’t have to wait so long when testing. Also, the <httpTransport /> element must be last otherwise WCF complains.
Once you have your itinerary, you can deploy it as normal and you’re ready to test.
Step 5 – Testing
As I said before, I have been using soapUI to test my service. The result I get when I call my service conforms to my response schema as below.
<ns0:Response xmlns:ns0="http://ESB.ExceptionHandling.ResponseMessage">
<ResponseElement1>The operation timed out while communicating with a sub-system.</ResponseElement1>
<ResponseElement2>The request channel timed out attempting to send after 00:00:10. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout.</ResponseElement2>
<ResponseElement3>http://namespace.org/AnAction</ResponseElement3>
</ns0:Response>
As you can see, my example map just extracts values from the SOAP Fault and puts them into my response message.
Conclusion
You now have all the tools you need to manage and control the way your services react to exceptions while consuming WCF services via an itinerary.
I hope you find this useful.
In the next edition, I will be looking at a hybrid approach that uses itineraries and orchestrations. I will also be looking at ways to handle exceptions that occur within your own itineraries. You can view these here:
BizTalk ESB Exception Handling – Consuming WCF
Services – Part II
BizTalk ESB Exception Handling – Consuming WCF
Services – Part III