An interesting challenge I have recently faced has inspired me to get back to the blog after a bit of a hiatus (my wife and I are expecting our first child very soon so blogging opportunities might be rare for awhile) and share some interesting experiences I’ve encountered on a recent project.  I have been tasked with standing up public facing one-way BizTalk WCF services that make use of a custom WCF behavior for authorization purposes, a schema validation pipeline component and the BRE Pipeline Framework for more complex validation.  The idea is that if any of the validations fail BizTalk should not accept the message and should instead return a fault to the client, and if all the validation succeeds then BizTalk sends back an empty response to the client (remember that a one-way BizTalk WCF receive location still involves a response being returned, it’s just that the response is empty).

With a naive outlook this is very easy to implement. All you have to do is turn on “Include Exception Details in Faults” on the messages tab of the receive location configuration and any exceptions encountered on your WCF receive location will now result in a SOAP fault being returned to the client with the exception contained within the details section of the message.

IncludeExceptions

However there are many problems with this approach.

  • The exception in the message details section contains way too much information, including the pipeline name and fully qualified assembly details, as well as a stack trace which should be considered sensitive internal information and not shared with consumers.
  • The exception type contained within the fault detail is Microsoft.BizTalk.Message.Interop.BTSException.  I don’t see this as being a good exception type, seeing as I don’t even want my consumers to know that the WCF service they are calling is hosted by BizTalk.  I would much rather throw a meaningful exception type.
  • The exception is not very friendly; while it contains the error details that the service consumer requires to troubleshoot the issue, it doesn’t contain an error code or an error type which the consumers might find helpful.
  • If the client was using a messaging engine like BizTalk rather than calling the service programmatically, and if they wanted to route the error to a exception handler rather than suspend the messaging instance, then they would be forced to make use of an orchestration that persists the identifier fields in the request message, so that they can inject those details into the exception before routing it to the exception handler.  If they didn’t do this (or something else creative) then the exception would contain no context and thus be rather useless to operational staff responding to it.

An example error message that the service would return in the case of an XML validation failure is as below.  Note that the fully qualified pipeline name is included, as is the fully qualified assembly name, the specific pipeline component that failed, and an entire stack trace.  I think almost anyone would recognize that this isn’t good enough for a publicly exposed service.

RawException

I decided to tackle these problems utilizing the below solutions, which I will describe in more detail further into this series.  I am sure there are other ways to approach these issues but this has worked quite well for me.

  1. Create a .NET class decorated with DataContract/DataMember attributes that represents a custom exception and contains all the details that I might want to return to a service consumer.
  2. Expose my custom typed fault in my WSDL to ensure that consumers are able to properly consume the exception detail.  For BizTalk consumers this would mean that generating schemas based on the WSDL via the “Add generated items/Consume WCF Service ” wizard would result in a schema corresponding to the custom exception being created as well.  For .NET (or other programmatic consumers) consumers this would mean that adding a service reference would now result in proxy classes representing the custom exception also being created.  I implemented this with a slight variation to the WsdlExportExtension service behavior described by Paolo Salvotori in this article that describes how to throw WCF typed faults in a BizTalk orchestration solution.
  3. Create a custom service behavior which enlists a custom error handler that inspects the out of the box error that is being returned to the client and replaces it with a new System.ServiceModel.FaultException<CustomException> where CustomException refers to the class that I created in the above step, and then populates the custom exception with the relevant details.
  4. Lastly I implemented an endpoint behavior that kills two birds in one stone.  First of all the endpoint behavior copies the SOAP action value from the request message to the response.  This is very important as the response message returned by BizTalk must hold the same SOAP action as the original request.  I will explain why this is important and illustrate this with examples further into the series.  The endpoint behavior also copies over a custom header value from the request to the response which provides BizTalk consumers (or other messaging engine type consumers who face a similar challenge) with a means of ensuring that exception messages returned by the service contain enough detail to route the message to an exception handling framework with no enrichment required from the original request.

As this is a rather complex solution and makes for a long read I have broken up this blog post into a series.  While I can’t provide the source code for this solution I shall share some code snippets and implementation details that should help you get going.  If you are interested in seeing how I’ve faced the aforementioned problem then continue on with the following parts of this series.

Part 2: Creating the typed fault

Part 3 : Publishing the typed fault contract in the WSDL

Part 4 : Creating a custom error handling service behavior

Part 5 : Creating an endpoint behavior to copy headers and the SOAP action from the request to the response

Part 6 : The client experience