Monday, July 27, 2009

Unity lifetime managers and WCF Integration

Although I have not written the final part of my BizTalk Fault Message support posts, I recently came across an issue that I thought needed to be shared.  I'm currently working on a medium-to-large project using NHibernate for data access.  One of the pillars of NHibernate development is session management.  Our data access is done across WCF services, using Unity for injecting the NHibernate sessions into our repositories.  Naively, our sessions were using the per-thread lifetime manager included with Unity.  This seemed to work great to select our data.  Unfortunately, when we began to update our data, a very strange issue would show up occasionally.

My developers began to tell me that every so often, a NonUniqueObjectException would be thrown when they tried to update a given entity.  The problem was that they could never reproduce it, nor were the steps the same every time.  I put this issue on the back burner so I could finish building out a service operation, when all of a sudden, I got the same error.

In my case, I was selecting a record with the first service call, and updating the same record with my second service call.  This simple set of service calls surfaced the issue, and it was reproducible about 80% of the time.  After spending a couple of minutes looking at the stack trace and generally debugging the issue, it finally dawned on me what was happening.  WCF's threading model, which was designed to make my services speedy, was actually hurting me in this case.

I don't know the exact details of how (for a good explanation of WCF IOCP threading internals, go here), but in a nutshell, the same thread that was obtained in my first service call was being used to service my second service call.  Because I was using the per-thread lifetime manager in Unity, the NHibernate session was not being cleaned out until the thread goes away.  Well, in WCF, the threads don't really go away.  Uh oh!

After conferring with Scott, we came up with the idea to build a Unity lifetime manager tied to WCF's OperationContext.  That way, our NHibernate session would live only for the life of the request, rather than the thread.

Starting with IExtension<OperationContext> and an implementation of IDispatchMessageInspector, I soon had an implementation of a UnityOperationContextLifetimeManager.  This is the one we ended up using to support our NHibernate sessions.  Once we used this lifetime manager instead of the per-thread lifetime manager, the NonUniqueObjectExceptions disappeared.

Once that was done, I figured since I did one of the extensible objects in WCF, I might as well do the rest.  The attached zip has lifetime managers to support all of the IExtensibleObject<T> implementations within WCF.

All of the lifetime managers are fairly straightforward, except for the UnityInstanceContextLifetimeManager.  This depends on the InstanceContext of a given service on which it is enabled.  PerSession is the default instancing mode for InstanceContext, but only when the transport supports sessions.  If the transport does not support sessions, the instancing mode is essentially PerCall.

Please note that the testing application is not necessarily a best practice for self-hosting a service, but I wanted you to see how to use the lifetime managers without having to worry about IIS (since IIS 6 doesn't have WAS and doesn't support the net.tcp binding).  Make sure if you are on Vista, you run this sample application as admin.

Download - UnityWcfExtensions.zip

Sunday, June 7, 2009

BizTalk 2006 R2 and WCF Fault Messages, Part 2

In my last post, I showed you how to return typed fault exceptions from BizTalk 2006 R2.  Unfortunately, that only solves half of the problem.  The second half of the problem is how to surface those typed faults in the WSDL generated by BizTalk when publishing an orchestration as a web service.  This allows the client side proxy class to properly generate the client side bits that will tie to the typed fault exception.  In this post, I will explain how the BizTalk WCF Publishing Wizard works at a high-level, and how to get the metadata information we need to build the WSDL returned to the clients.  The last post in this series will explain how to get this metadata into the actual WSDL.

Disclaimer:  The explanation below is my understanding of how the BizTalk WCF Publishing Wizard and the BizTalk runtime interact to publish an orchestration as a WCF.  As I am not a member of the BizTalk product team, I am only inferring this information from my investigations.

To add a bit of background, let's look at how BizTalk publishes an orchestration as a service.

First, we run the BizTalk WCF Publishing Wizard on our compiled assembly.  This outputs a few files into the virtual directory that will host our service.  The only one we are concerned with, at this point, is the ServiceDescription.xml.

The ServiceDescription.xml file is the file that gets used by the BizTalk WSDL exporter to generate the actual WSDL that is surfaced to clients.  The problem is that this file does not contain any of the fault information from BizTalk.  This also means that the BizTalk WSDL exporter doesn't have the ability to surface this information even if it existed.

Because of the above issues, our problem is twofold:

  1. We need to get the fault information out of the BizTalk assembly
  2. We then need to put that fault information into the generated WSDL from BizTalk

The first problem actually turns out to be easier to solve than first anticipated.  The BizTalk assemblies contain metadata about the orchestrations, maps, pipelines, etc. and there are BizTalk specific classes that can be used to parse this information (that's how the wizard works).  I found that because of this, the code inside the WCF Publishing Wizard does actually retrieve the fault information, but it is stripped out when the ServiceDescription.xml is written to disk.

My goal was to solve problem #1 in a similar way as the WCF Publishing Wizard.  I wrote a command-line application that parses a BizTalk assembly (using the same classes as the WCF Publishing Wizard) to generate the assembly metadata.  It then parses through that metadata and writes a file called BtsFaultDescription.xml (you can, of course, name it whatever you wish).  This XML file contains the fault information for the ports inside our BizTalk orchestration and will be used to generate the fault information in the WSDL of our service.

The code below shows a modified form of this tool:

(Note:  I can't give out the exact code we use internally, as it contains proprietary information, but I have given as much as needed to solve the problem at hand.)

static void Main(string[] args)
{
    if ((args == null) || (args.Length != 1))
    {
        Console.WriteLine("Usage:  BtsFaultPublisher.exe <assembly_name>");
        return;
    }
 
    string assemblyPath = args[0];
 
    if (!File.Exists(assemblyPath))
    {
        Console.WriteLine("Error:  Assembly '{0}' does not exist.", assemblyPath);
        return;
    }
 
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    BizTalkAssembly bizTalkAssembly = BizTalkAssemblyFactory.Create(assembly);
    bizTalkAssembly.Merge = MergeType.PerAssembly;
    BizTalkAssemblyImporter importer = new BizTalkAssemblyImporter();
    WcfServiceDescription wcfServiceDescription = importer.Import(assembly.Location, bizTalkAssembly);
 
    BtsFaultDescription btsFaultDescription = new BtsFaultDescription();
    List<PortType> portTypes = new List<PortType>();
    foreach (WcfService wcfService in wcfServiceDescription.WcfServices)
    {
        PortType portType = new PortType();
        portType.Name = wcfService.Name;
        List<Operation> operations = new List<Operation>();
        foreach (WcfOperation wcfOperation in wcfService.WcfOperations)
        {
            Operation operation = new Operation();
            operation.Name = wcfOperation.Name;
            List<FaultMessage> faultMessages = new List<FaultMessage>();
            foreach (WcfMessage wcfMessage in wcfOperation.WcfMessages)
            {
                if (wcfMessage.Direction == WcfMessage.WcfMessageDirection.Fault)
                {
                    FaultMessage faultMessage = new FaultMessage();
                    faultMessage.Name = wcfMessage.WcfMessageType.TypeName + "#" + wcfMessage.WcfMessageType.RootName;
                    faultMessage.Description = wcfMessage.WcfMessageType.TypeName + ", " + wcfMessage.WcfMessageType.AssemblyName;
                    faultMessage.FileName = @".\" + wcfMessage.WcfMessageType.TypeName + ".xsd";
                    faultMessage.TypeName = wcfMessage.WcfMessageType.TypeName;
                    faultMessage.AssemblyName = wcfMessage.WcfMessageType.AssemblyName;
                    faultMessage.AssemblyLocation = wcfMessage.WcfMessageType.AssemblyLocation;
                    faultMessage.TargetNamespace = wcfMessage.WcfMessageType.TargetNamespace;
                    faultMessage.RootName = wcfMessage.WcfMessageType.RootName;
                    faultMessages.Add(faultMessage);
                }
            }
 
            if (faultMessages.Count > 0)
            {
                operation.FaultMessage = faultMessages.ToArray();
                operations.Add(operation);
            }
        }
 
        if (operations.Count > 0)
        {
            portType.Operation = operations.ToArray();
            portTypes.Add(portType);
        }
    }
 
    if (portTypes.Count > 0)
    {
        btsFaultDescription.PortType = portTypes.ToArray();
    }
 
    XmlSerializer serializer = new XmlSerializer(typeof(BtsFaultDescription));
    using (XmlTextWriter xmlTextWriter = new XmlTextWriter("BtsFaultDescription.xml", new System.Text.UTF8Encoding(false)))
    {
        xmlTextWriter.Formatting = Formatting.Indented;
        serializer.Serialize((XmlWriter)xmlTextWriter, btsFaultDescription);
        xmlTextWriter.Flush();
    }
 
    Console.WriteLine();
    Console.WriteLine("BtsFaultDescription.xml was written successfully.  Put this file in the App_Data folder of the BTS WCF Service that has been fault-enabled.");
}

The project needs a reference to Microsoft.BizTalk.Adapter.Wcf.Publishing.dll and the following classes.  I put all of them in a file called BtsFaultDescription.cs in a common assembly as these classes are used in this project, as well as the project that writes the actual WSDL.

[Serializable, XmlType(AnonymousType = true), XmlRoot(Namespace = "http://mycompany.com/BizTalk/2009/03/WcfAdapter/Publishing", IsNullable = false), DesignerCategory("code"), GeneratedCode("xsd", "2.0.50727.42")]
public class BtsFaultDescription
{
    private PortType[] portType;
 
    [XmlElement("PortType", Form = XmlSchemaForm.Unqualified)]
    public PortType[] PortType
    {
        get { return this.portType; }
        set { this.portType = value; }
    }
}
 
[Serializable, XmlType(Namespace = "http://mycompany.com/BizTalk/2009/03/WcfAdapter/Publishing"), GeneratedCode("xsd", "2.0.50727.42"), DesignerCategory("code")]
public class Service
{
    private string name;
    private PortType[] portType;
 
    [XmlAttribute]
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }
 
    [XmlElement("PortType", Form = XmlSchemaForm.Unqualified)]
    public PortType[] PortType
    {
        get { return this.portType; }
        set { this.portType = value; }
    }
}
 
[Serializable, XmlType(Namespace = "http://mycompany.com/BizTalk/2009/03/WcfAdapter/Publishing"), GeneratedCode("xsd", "2.0.50727.42"), DesignerCategory("code")]
public class PortType
{
    private string name;
    private Operation[] operation;
 
    [XmlAttribute]
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }
 
    [XmlElement("Operation", Form = XmlSchemaForm.Unqualified)]
    public Operation[] Operation
    {
        get { return this.operation; }
        set { this.operation = value; }
    }
}
 
[Serializable, XmlType(Namespace = "http://mycompany.com/BizTalk/2009/03/WcfAdapter/Publishing"), GeneratedCode("xsd", "2.0.50727.42"), DesignerCategoryK/span>("code")]
public class Operation
{
    private string name;
    private FaultMessage[] faultMessage;
 
    [XmlAttribute]
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }
 
    [XmlElement("FaultMessage", Form = XmlSchemaForm.Unqualified)]
    public FaultMessage[] FaultMessage
    {
        get { return this.faultMessage; }
        set { this.faultMessage = value; }
    }
}
 
[Serializable, XmlType(Namespace = "http://mycompany.com/BizTalk/2009/03/WcfAdapter/Publishing"), GeneratedCode("xsd", "2.0.50727.42"), DesignerCategory("code")]
public class FaultMessage
{
    private string assemblyLocation;
    private string assemblyName;
    private string description;
    private string fileName;
    private string name;
    private string rootName;
    private string targetNamespace;
    private string typeName;
 
    [XmlAttribute]
    public string AssemblyLocation
    {
        get { return this.assemblyLocation; }
        set { this.assemblyLocation = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string AssemblyName
    {
        get { return this.assemblyName; }
        set { this.assemblyName = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string Description
    {
        get { return this.description; }
        set { this.description = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string FileName
    {
        get { return this.fileName; }
        set { this.fileName = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string Name
    {
        get { return this.name; }
        set { this.name = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string RootName
    {
        get { return this.rootName; }
        set { this.rootName = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string TargetNamespace
    {
        get { return this.targetNamespace; }
        set { this.targetNamespace = (string)(value ?? null); }
    }
 
    [XmlAttribute]
    public string TypeName
    {
        get { return this.typeName; }
        set { this.typeName = (string)(value ?? null); }
    }
}

Most of this code was lifted almost verbatim from the publishing wizard, so please forgive some of the code oddities.  Also, be aware that this code was designed to solve one specific scenario, so some of the generation options are hardcoded inside the application.  Eventually, I may extend this to encompass other scenarios, but I thought it was more important to get this information out there.

Another important thing to remember is that the BtsFaultDescription.xml file MUST be regenerated whenever fault information is added/changed/removed in order to keep the generated WSDL in sync with the underlying implementation.  This file is the metadata glue that allows this solution to work.

In the next post, I will show how to actually inject the information in the BtsFaultDescription.xml into the WSDL of the BizTalk WCF Service.

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.