Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / biztalk

BizTalk ESB Exception Handling – Consuming WCF Services – Part I

5.00/5 (3 votes)
3 Nov 2012CPOL10 min read 28.9K   198  
Managing exceptions when consuming WCF services via the BizTalk ESB Toolkit

Download Example Solution

Introduction

So you've been building BizTalk services using the ESB Toolkit have an ESB itinerary, it’s consuming a WCF service and you’ve found that it all works very well. Then, your first exception occurs and your itinerary doesn’t do what you expect it to. If that’s you, then I think I can help.

Background - BizTalk / ESB Toolkit 101

Before we go any further, there are a few points about BizTalk and the ESB Toolkit that are useful to know.

When BizTalk receives a message, the message is only serialized to the MessagBox after the pipeline has completed its job.

This means that, after all the maps and transforms in your itinerary, only the final resultant message will ever reach the MessageBox. The original message and intermediate messages are lost.

Another interesting fact I bumped into when calling a WCF service from an itinerary is that, when the WCF adapter raises an exception, the receive pipeline of the send port does not execute. The first time I saw this was when trying to authenticate a service request when the target service refused my credentials.

Lastly, itinerates normally execute in the pipelines not orchestrations. You can make them execute in an orchestration but that’s not the scenario I am looking at today.

So, if you expose a web service that uses an itinerary, any message received is processed in the receive pipeline under the host which the WCF adapter is configured to run. Usually the isolated host (IIS).

When sending messages to another service, they are processed by the send pipeline executing within a BizTalk host.

There are other scenarios but these are the most common in the environments I have been working.

The Problem - Trapping Exceptions

To help explain, here is a diagram of the scenario I was working on when I discovered the problem I'm discussing. You can see that I have a BizTalk service in the middle and this consumes a WCF service.

Image 1

During my testing of this service, I wanted to check that it provided the desired response under certain error conditions. What I found when receiving an exception from a downstream service is that the result was far from what I expected. I thought that the itinerary would execute on receiving the error and I could map to something more meaningful to the consumers of my service. However, what I found was that the consumer of my service received the exact same error message I received from my downstream service i.e. my consumers got the “credentials failed” error that I got from the downstream service. This was a major problem for me.

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.

Image 2

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:

C#
protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)
{
    //Create the default channel
    TChannel innerChannel = _innerChannelFactory.CreateChannel(to, via);

    //Make sure we are working with a request channel
    if (typeof(IRequestChannel) == typeof(TChannel))
    {
        //Create and return our wrapper channel. This wrapper is where we trap and manage
        //  exceptions
        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:

C#
//Grab a buffer from the message. We use this when creating the response
//  when an exception occurs
MessageBuffer messageBuffer = message.CreateBufferedCopy(int.MaxValue);

try
{
    //By grabbing the buffer above, we can't use the original message so we
    //  create a copy to use when consuming the service
    Message messageToSend = messageBuffer.CreateMessage();

    /*
     * 
     * Here is where we use the inner channel to consume the service
     * 
     */
    reply = InnerChannel.Request(messageToSend);

    string messageString = reply.ToString();

    //If the response contains the word "fault" then we assume we have received an error
    //  NOTE: This needs to be reworked to cater for valid response messages that
    //        contain the word "fault". An exercise for another day.
    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 an exception so that our exception handler can take over
        throw new SoapException(faultstring,
                        SoapException.ClientFaultCode,
                        action,
                        detailXmlNode);
    }
}
catch (SoapException exception)
{
    //Create the reply message from the exception
    reply = CreateResponseMessage(messageBuffer.CreateMessage(),
                        "An error occured comunicating with a sub-system.",
                        exception);
}
catch (CommunicationException exception)
{
    //Create the reply message from the exception
    reply = CreateResponseMessage(messageBuffer.CreateMessage(),
                        "An general error occured comunicating with a sub-system.",
                        exception);
}
catch (TimeoutException exception)
{
    //Create the reply message from the exception
    reply = CreateResponseMessage(messageBuffer.CreateMessage(),
                        "The operation timed out while communicating with a sub-system.",
                        exception);
}
catch (Exception exception)
    //Create the reply message from the 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.

C#
private Message CreateResponseMessage(Message message, string errorMessage, Exception exception)
{
    string action = null;

    if (message != null)
        action = message.Headers.Action;

    //Create a reply message by using our custom BodyWriter
    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)
        {
            //Copy the properties from the source messages to the new message
            replyMessage.Properties.CopyProperties(message.Properties);
        }
    }

    //Generate a new message ID
    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.

XML
<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.

XML
<system.serviceModel> 
  <extensions>
    <bindingElementExtensions>
      <!-- other extensions here -->
      <add name="ESBExceptionHandlingChannel" type="ESB.ExceptionHandling.ServiceModel.Configuration.ServiceBindingExtensionElement, ESB.ExceptionHandling.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a496c9267b296339" />
      <!-- other extensions here -->
    </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.

Image 3

Image 4

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.

Image 5

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 ConfigurationBindingConfiguration=<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.

XML
<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.

XML
<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

Points of Interest

I have a much better understanding of the WCF Stack and how it can be extended to best suit my needs. Especially within the BizTalk arena. The opportunities are endless.

History

Version 1.0

Version 1.1 - Added links to parts II and III

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)