Saturday, May 16, 2009

BizTalk 2006 R2 and WCF Fault Messages, Part 1

Recently for a project, I needed to return a typed fault message from a BizTalk 2006R2 orchestration published as a WCF Service.  After doing some reading, I was more than a little surprised that BizTalk 2006R2 doesn’t support custom WCF typed faults.

After some searching, I came across Wouter Crooy’s post on how to return typed fault messages from BizTalk.  While the steps outlined in the post did indeed work, it still didn’t seem like the “right” solution. 

I felt that since we were publishing the orchestration as a WCF service, the better direction would be to use the built-in WCF extensibility mechanisms to change the behavior of the adapter, and make BizTalk do what I thought it should do.  This is the solution I outline below.

Btw, this post assumes some basic knowledge of BizTalk 2006 R2 and WCF’s extensibility points, as well as an already existing BizTalk Visual Studio project.

  1. Create schemas for your request, response, and fault “contracts”.  The request and response can be whatever fits your needs.  The fault schema is the one we are focusing on in this post.  I have posted mine as a sample below (Note:  I have promoted both child elements as distinguished to make setting them in my BizTalk orchestration easier).
    <?xml version="1.0" encoding="utf-16"?>
    <xs:schema xmlns:b="http://schemas.microsoft.com/BizTalk/2003" xmlns="http://BtsWcfService.BtsFaultMessage" elementFormDefault="qualified" targetNamespace="http://BtsWcfService.BtsFaultMessage" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="BtsFaultMessage">
        <xs:annotation>
          <xs:appinfo>
            <b:properties>
              <b:property distinguished="true" xpath="/*[local-name()='BtsFaultMessage' and namespace-uri()='http://BtsWcfService.BtsFaultMessage']/*[local-name()='ErrorCode' and namespace-uri()='http://BtsWcfService.BtsFaultMessage']" />
              <b:property distinguished="true" xpath="/*[local-name()='BtsFaultMessage' and namespace-uri()='http://BtsWcfService.BtsFaultMessage']/*[local-name()='ErrorMessage' and namespace-uri()='http://BtsWcfService.BtsFaultMessage']" />
            </b:properties>
          </xs:appinfo>
        </xs:annotation>
        <xs:complexType>
          <xs:sequence>
            <xs:element name="ErrorCode" type="xs:string" />
            <xs:element name="ErrorMessage" type="xs:string" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  2. Create a simple orchestration that returns a fault using the built-in fault mechanisms on the request-response ports and a message of the above schema type.  In my orchestration, the request contains a Boolean value that drives the decision shape on whether or not to send a fault message.clip_image002
  3. Deploy the BizTalk application and publish your orchestration as a WCF service using the WCF-BasicHttp adapter.
  4. Build a test client to call your service (You could use wcftestclient too, but I wanted more control for debugging and analysis.  I used VS2008, as the WCF tooling is better than VS2005).

    (Note:  Steps 4 - 6 follow my trek through BizTalk + WCF.  They are not integral to the solution except as insight into how BizTalk and WCF interact.  If you want to jump directly to the solution part of the post, skip to step 7.)

    Run the test client and cause a SOAP fault to be returned from BizTalk.  We will get an exception, but it's not quite what we would expect.  Instead of an exception containing the message that we defined in our BizTalk orchestration, it is a "plain" System.ServiceModel.FaultException generated by BizTalk with the following message:

    "The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs."

    This is exactly the behavior we should expect at this point due to the way BTS handles WCF faults by default.
  5. The exception in step #4 is normal if you are using the default configuration of your WCF-BasicHttp adapter as produced by the WCF publishing wizard.  In order to not publish potentially sensitive information in the exception, BizTalk errs on the side of caution and simply returns a message explaining how to turn on some debugging bits to help figure out what is causing your issues.

    However, in order to return "real" typed fault exceptions, we need BizTalk to do a little more work for us.  Follow these quick steps below to enable more detailed fault exceptions.

    In the BizTalk Administration Console, right-click on the service's receive location and select properties
    Click "Configure…"
    Go to the Messages tab and make sure "Include exception detail in faults" is checked.
    Hit OK twice.
  6. Run your test client again.  As we would expect, we get an exception again, but this time, it's a little different.  Instead of a System.ServiceModel.FaultException, it is now a TYPED System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail> exception.  If we examine the Message property of this exception, it is now the Xml serialized version of the BtsFaultMessage instance we specified as our fault within our BizTalk orchestration!

    We now have BizTalk returning the data that we need, but it's still not in the right format for WCF to consume it the way we would expect.  There is nothing more BizTalk can do to help us out.  The WCF adapters do not support typed FaultException instances (EXCEPT for the one we've just seen).  What we need to do, in a nutshell, is to intercept the message coming out of the WCF adapter in BizTalk BEFORE it gets sent down to the client.  This sounds like a job for an IDispatchMessageInspector!
  7. I won't go into detail about WCF behavior extension elements (see Paolo Pialorsi's post for more detail), but to make this solution work, we will need a BehaviorExtensionElement descendant, an IEndpointBehavior implementation, and an IDispatchMessageInspector implementation.

    In order to make this work properly for BizTalk, the BehaviorExtensionElement needs to be registered in the .NET Framework 2.0 machine.config. This way, the behavior will be visible in the BizTalk admin console.
  8. Once you have a skeleton IDispatchMessageInspector set up, the code we need to worry about will be in the BeforeSendReply method of the IDispatchMessageInspector interface. In this method, BizTalk is finished with the message and has sent it through the WCF pipeline for delivery. This is where we will change the message to be WCF typed-fault compatible. The code below is essentially what needs to happen (some error checking has been removed to not cloud the intent of the code).
    public void BeforeSendReply(ref Message reply,
          object correlationState)
    {
       if (reply.IsFault)
       {
          //Make a copy.
          MessageBuffer buffer = reply.CreateBufferedCopy(int.MaxValue);
                    
          //Get the MessageFault from the message.
          MessageFault messageFault = MessageFault.CreateFault(buffer.CreateMessage(), int.MaxValue);
    
          //If we don't have detail, there is no BizTalk fault message, so return the original message.
          if (!messageFault.HasDetail)
          {
             reply = buffer.CreateMessage();
             return;
          }
    
          ExceptionDetail exceptionDetail = messageFault.GetDetail<ExceptionDetail>();
          XmlDictionaryReader detailsReader = XmlDictionaryReader.CreateDictionaryReader(XmlReader.Create(new StringReader(exceptionDetail.Message)));
          try
          {
             //We need to wrap the fault in a compatible WCF fault
             reply = Message.CreateMessage(reply.Version,
                new BizTalkMessageFault(
                new FaultCode("Client"),
                new FaultReason("BTSError"),
                detailsReader), null);
          }
          finally
          {
             detailsReader.Close();
          }
      }
    }

    The BizTalkMessageFault class simplifies the writing of the fault message.  It makes WCF do the hard work for us.

    public class BizTalkMessageFault : MessageFault
    {
        private FaultCode faultCode;
        private FaultReason faultReason;
        private string details;
    
        public BizTalkMessageFault(FaultCode faultCode, FaultReason faultReason, XmlDictionaryReader detailsReader) : base()
        {
            Guard.ArgumentNotNull(faultCode, "faultCode");
            Guard.ArgumentNotNull(faultReason, "faultReason");
            Guard.ArgumentNotNull(detailsReader, "detailsReader");
    
            this.faultCode = faultCode;
            this.faultReason = faultReason;
    
            //Move to the outer element of the message xml
            if (detailsReader.MoveToContent() == XmlNodeType.Element)
            {
                this.details = detailsReader.ReadOuterXml();
            }
        }
    
        public override FaultCode Code
        {
            get { return this.faultCode; }
        }
    
        public override FaultReason Reason
        {
            get { return this.faultReason; }
        }
    
        public override bool HasDetail
        {
            get { return true; }
        }
    
        protected override void OnWriteDetailContents(XmlDictionaryWriter writer)
        {
           Guard.ArgumentNotNull(writer, "writer");
           writer.WriteRaw(this.details);
        }
    }
  9. Once you get this behavior built and installed, it's time to test it within BizTalk.  The first thing we need to do change our adapter on our receive location.  The reason for this is because we cannot add an endpoint behavior to the WCF-BasicHttp adapter.  Change the adapter to WCF-CustomIsolated and configure the adapter as follows:

    Change the binding type to basicHttpBinding
    Add our endpoint behavior
    clip_image002
    Don't forget to include exception detail in faults
    clip_image002[4]
  10. Now, rerun the client application.  As before, we get an exception, but now the exception is a plain System.ServiceModel.FaultException again.  But, the message does say "BTSError", which is the FaultReason created in our WCF endpoint behavior.  So, that means our behavior is indeed replacing the message coming out of BizTalk, but why does this exception not contain all of the message data we returned from BizTalk?  In actuality, the entire message IS returned from BizTalk (turn on WCF diagnostic logging in your test application and you will see the whole message is indeed sent).

    What is happening is that the client side doesn't understand the shape of the message being returned from BizTalk, so it simply throws away the unrecognized bits.  So how do we make the client recognize the returned message?  We need to define a data contract that describes the message detail that is being sent from BizTalk.  This data contract is actually nothing more than the BizTalk schema definition, but decorated so WCF understands it.  I have included mine below:
    [DataContract(Name = "BtsFaultMessage", Namespace = "http://BtsWcfService.BtsFaultMessage")]
        public class BtsFaultException
        {
            [DataMember(Order = 0)]
            public string ErrorCode
            {
                get;
                set;
            }
    
            [DataMember(Order = 1)]
            public string ErrorMessage
            {
                get;
                set;
            }
        }
  11. Open the Reference.cs file in your test application that contains the WCF proxy to our BizTalk Service and add the System.ServiceModel.FaultContract attribute to the appropriate operation on your service interface.  It should be similar to the following
    [System.ServiceModel.FaultContract(typeof(BtsFaultException))]
  12. Rerun your test client, but add the catch block below to catch the new, TYPED fault coming from BizTalk.
    catch (FaultException<BtsFaultException> ex)
    {
        Console.WriteLine("ErrorCode: {0}   ErrorMessage: {1}", ex.Detail.ErrorCode, ex.Detail.ErrorMessage);
    }
  13. Your client application should catch the new typed faults, and you will now be able to access the data sent back in the BizTalk fault message.

This post showed you how to return typed fault exceptions from BizTalk 2006 R2. However, editing the generated service proxy for my imported WCF service is not a good practice, even to enable faults. In my next post, I will show you how to extend our behavior to add the soap faults directly into the WSDL generated by BizTalk.

Tuesday, May 12, 2009

Post #1

After putting it off for years, I’ve actually started a blog (Yes, Kirk.  I finally did it).  Here you will find posts on C#, BizTalk, WCF, and almost any other .NET-related technologies.  I hope you find some information here that is helpful.  I'm not 100% happy with the code formatting at this point.  However, I want to get some information out there, so please bear with me until I get the formatting a little cleaner.

My first couple of posts will focus on how to return typed WCF Faults from a BizTalk 2006 R2 orchestration as a WCF service.  It came as quite a surprise that this is not supported out-of-the-box, and it is not readily apparent how this can be accomplished.  While you are waiting for the upcoming posts, please visit some of the blogs that I am following for more great .NET news and information.