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 III

5.00/5 (1 vote)
3 Nov 2012CPOL6 min read 20K   151  
Managing exceptions when consuming WCF services via the BizTalk ESB Toolkit - Part III

Download Example Solution

Introduction

This is part 3 of my 3 part series on exception handling with the ESB Toolkit. In this last article I will explain how to create your own error messages when your service fails. That is, when an error occurs in one of you own components… how do you handle this?

I’m not the first person to look at this and I took some of the ideas that Paolo Salvatori blogged about here.

The Problem - Catching Exceptions

Here is the scenario I was working on when I discovered the problem. You can see that I have a BizTalk service. I have exposed this as a WCF service.

Image 1


During my testing of this service, I wanted to check that it provided the desired response when an error occurred within my itinerary. I found that the response I got contained information that I didn’t want my consumers to receive. For instance, when a map failed, the full reason for failure was available to my consumer. This is pretty much standard BizTalk pipeline behavior. However, what I wanted to do is hide the details of the error from my consumers.

Itinerates 101

Before we continue, I just want to reiterate here that itinerates execute in BizTalk pipelines. So, when an error occurs in you itinerary there is no way to catch the exception and you can’t the do anything with it. To overcome this, we need to configure a hook into the adapter to get to the exception.

The Solution – WCF Error Handler

I took the advice from Paolo and created my own IErrorHandler. In it I take any errors and convert them to a SOAP Fault. I was looking for a generic solution and decided that this was best. From there, I extended Paolo’s idea and added the ability to execute a map in the error handler. Here’s how I got it working.

Step 1 – Create the Error Handler

I’m still using BizTalk 2009, Visual Studio 2008 and the ESB Toolkit 2.0 but I’m sure you can get this to work with 2010 and 2.1 respectively.

So, I have a plain old assembly that contains the following types.

Image 2


The solution all starts with the ErrorHandlerBehaviorExtensionElement class. This is a configuration element that we will be referencing when we setup our receive port. I have added a custom property to this class so that we can configure an optional map to execute after the exception has been converted into a SOAP Fault.

The Extension Element provides an instance of the ErrorHandler class which implements both IServiceBehavior and IErrorHandler. The error handler will convert the exception into a SOAP Fault by using the ErrorBodyWriter. It will also execute a map if configured.

The ability to execute a map has been provided because the Error Handler is executed in the bowels of the WCF adapter. It is no longer in BizTalk or even a pipeline and, as such, we loose any ability to process the message via an itinerary or orchestration.

Implementing of the IErrorHandler is where the guts of the solution sits. Here is the interface of the IErrorHandler.

Image 3


For the HandlError method, I just trace the error for debugging purposes. In the ProvideFault method is where we create our custom response message. Here is the code:

C#
public bool HandleError(Exception error)
{
    //Tracing goes here...

    //WCF must NOT abort the session   
    return true;
}
 
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
    try
    {     
        //Create our custom fault message       
        fault = CreateErrorMessage(fault, "An unexpected error occurred", error);
    }
    catch (Exception exception)
    {
        //Create our custom fault message
        fault = CreateErrorMessage(fault, "An unexpected error occured", error);
    }
}  

I use a private method called CreateErrorMessage that creates a response message for us. In it, I manually execute a custom BodyWriter to build my fault. I then pass the result from the BodyWriter to a map if needed.

C#
private Message CreateErrorMessage(Message message, string errorMessage, Exception exception)
{
   string action = null;
 
   if (message != null &&       
       message.Headers != null)
        action = message.Headers.Action;
   
    string messageType = "http://schemas.xmlsoap.org/soap/envelope/#Fault";
   
    //Prepare to use our body writer to create a message
    StringBuilder stringBuilder = new StringBuilder();
   
    //Create the SOAP Fault body
    using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
    {
       using (XmlDictionaryWriter xmlDictionaryWriter =
                  XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter))      
       {
           //Create an instance of our body writer
           BodyWriter bodyWriter = new ErrorBodyWriter("soap:Receiver",
                                                       exception.Message,
                                                       action,
                                                       errorMessage,
                                                       exception);
 
           //Execute the body writer
           bodyWriter.WriteBodyContents(xmlDictionaryWriter);
 
           //Flush the stream
           xmlDictionaryWriter.Flush();
        }
    }
 
    if (!string.IsNullOrEmpty(Map))
    {
        //A map has been configured so let's execute it.
        //  Note: if executing the map fails in any way, we return the 
        //        SOAP Fault created above
        using (MemoryStream streamIn =
                    new MemoryStream(Encoding.Unicode.GetBytes(stringBuilder.ToString())))
        {
            //Perform the transform
            Stream streamOut = TransformStream(streamIn, Map, ref messageType);
 
            using (StreamReader streamReader = new StreamReader(streamOut))
            {
                //Get the contents of the transform stream
                stringBuilder = new StringBuilder(streamReader.ReadToEnd());
            }
        }
    }   
 
    TextReader textReader = new StringReader(stringBuilder.ToString());
 
    //Convert the raw message (stringBuilder) to a Message
    Message replyMessage = Message.CreateMessage(MessageVersion.Default,
                                                 messageType,
                                                 XmlReader.Create(textReader));
 
    //Return the message
    return replyMessage;
}

If we have the details of a map, then the ErrorHandler attempts to execute it. Any errors that happen when executing the map are ignored. We use some of the runtime types that you get with BizTalk to perform the mapping. Here is the method that does this.

C#
private Stream TransformStream(Stream streamIn, string mapName, ref string messageType)
{
    try
    {
        //Get the data type of the map
        Type type = Type.GetType(mapName);
 
        if (type == null)
            throw new Exception(string.Format("The type for {0} was not a valid map type.", mapName));
 
        //Load the map        
        TransformMetaData transformMetaData = TransformMetaData.For(type);
       
        //Get the schema information of the source message from the map
        SchemaMetadata schemaMetadataSource = transformMetaData.SourceSchemas[0];
       
        //Get the schema type of the soruce message in the map
        string schemaName = schemaMetadataSource.SchemaName;
 
        //Check that the message type of the input stream matches the message type of the
        //  source schema in the map
        if (!string.Equals(messageType, schemaName, StringComparison.InvariantCultureIgnoreCase))
        {
            //The message type is not the same as the map source schema so don't
            //  run the map
            return streamIn;
        }
 
        //Get the schema information of the target message from the map
        SchemaMetadata schemaMetadataTarget = transformMetaData.TargetSchemas[0];
 
        //Get the message type of the target of the map
        messageType = schemaMetadataTarget.SchemaName;
 
        //Prepare the input stream
        XPathDocument input = new XPathDocument(streamIn);
        XslTransform transform = transformMetaData.Transform;
 
        //Create the output stream
        Stream streamOut = new MemoryStream();
 
        //Perform the transform  
        transform.Transform(input, transformMetaData.ArgumentList, streamOut);
 
        //Finalise the output stream
        streamOut.Flush();
        streamOut.Seek(0L, SeekOrigin.Begin);
 
        return streamOut;
    }
    catch (Exception exception)
    {
 
        return streamIn;
    }
} 

I need to point out that our BodyWriter doesn’t produce the full SOAP Envelope as the BodyWriter in from the first article in this series does. This is because the error handler is only required to build the message body not the whole message envelope as we needed in part I.

Here is an example of the SOAP Fault that we will be creating:

XML
<Fault SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/"> 
  <faultcode>soap:Receiver</faultcode>
  <faultstring>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml,Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher"
Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</faultstring>
  <detail>
    <ErrorMessage>An unexpected error occured</ErrorMessage>
    <Exception>
      <Message>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml, Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher" Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</Message>
      <Type>Microsoft.BizTalk.Message.Interop.BTSException</Type>
    </Exception>
  </detail>
</Fault>

Step 2 – Deploying The Error Handler

To deploy, just compile the assembly and add it to the GAC. Then make the new behavior available to your service by adding it to the machine.config file like this.

XML
<system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <!-- other extensions here -->
      <add name="ESBErrorHandler"
           type="ESB.ExceptionHandling.ServiceModel.Configuration.ErrorHandlerBehaviorExtensionElement, 
                 ESB.ExceptionHandling.ServiceModel, Version=1.0.0.0,
                 Culture=neutral, PublicKeyToken=a496c9267b296339" />
      <!-- other extensions here -->
    </behaviorExtensions>
  </extensions>
</system.serviceModel> 

Only adding this to the BizTalk config file means that it won’t be accessible to IIS or the BizTalk console.

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.

I have taken the schema from part I of this series and, have created an example a map that I know will fail. I also use the SOAP to Response map I created in part I. The schema and maps have been deployed to BizTalk and will be executed by my itinerary.

My port configuration includes a WCF receive port called TestReceivePort with a receive location called TestReceiveLocation. The receive location uses the ItinerarySelectReceiveXml pipeline with the following settings:

Property Name

Value

ItineraryFactKey

Resolver.Itinerary

ResolverConnectionString

ITINERARY:\\name=ESB.ExceptionHandling.Test


To make use of the custom error handler, I have used WCF-CustomIsolated for the receive location type. This has been configured to use wsHttpBinding and I have set security to None.

Image 4

Then I add the custom behavior extension on the behaviors tab.

Image 5

Image 6

I configure the error handler to execute my SOAPFault_to_Response map from part I by entering the full type of the map.

Image 7

ESB.ExceptionHandling.Maps.SOAPFault_to_Response, ESB.ExceptionHandling, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a496c9267b296339

Lastly, I make sure that the error handling includes the fault details.

Image 8

I published the receive location to IIS using the WCF Publishing wizard and can now use soapUI to consume my service.


The rest is 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 before the off-ramp.


Image 9

The transform will fail because we will use my map that I know will fail. The off-ramp will never be reached so I’m not going to go into its configuration.

Once you have the itinerary, you can deploy it as normal and you’re ready to test.

Step 5 – Testing

As before, I use soapUI to test my service. The result I get when I call my service conforms to my response schema as below. The map generated an error that was converted to a SOAP Fault which, in turn,


was mapped to a response using the map I created in part I.

XML
<ns0:Response xmlns:ns0="http://ESB.ExceptionHandling.ResponseMessage">
  <ResponseElement1>An unexpected error occured</ResponseElement1>
  <ResponseElement2>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml, Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher" Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</ResponseElement2>
  <ResponseElement3/>
</ns0:Response></span>

Finally

This series of 3 parts gives you all the tools you need to manage and control the way your services react and create exceptions when consuming and publishing WCF services.

Although I have written these article with the ESB Toolkit in mind, some of the techniques I present can also be used when building services in non-ESB ways.

History

Version 1.0

License

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