How to Throw Typed Fault Exceptions from Orchestrations Published as WCF Services


Introduction

In general, a WCF Web Service can return two types of SOAP faults: typed and untyped SOAP faults.

Typed Faults

In order to throw a typed fault, a service operation must be decorated with a System.ServiceModel.FaultContractAttribute that specifies a fault data contract defined earlier. The following code snippet shows a WCF web service called HelloWorld which implements the IHelloWorld service or contract interface. This interface exposes a single synchronous operation called SayHello that is decorated with the FaultContractAttribute. The attribute declares that the SayHello method can throw a typed fault exception defined by the CustomError data contract class. The implementation of the SayHello method in the HelloWorld class checks if the incoming request is null and in this case it throws an error of type FaultException<CustomError>.

namespace Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.Services
{
    [Serializable]
    [DataContract(Name = "HelloWorldRequest", Namespace = "http://microsoft.biztalk.cat/10/helloworld")]
    public class HelloWorldRequest
    {
       ...
    }

    [Serializable]
    [DataContract(Name = "HelloWorldResponse", Namespace = "http://microsoft.biztalk.cat/10/helloworld")]
    public class HelloWorldResponse
    {
        ...
    }

    [Serializable]
    [DataContract(Name = "CustomError", Namespace = "http://microsoft.biztalk.cat/10/customerror")]
    public class CustomError
    {
        ...
    }

    [ServiceContract(Name = "HelloWorld", Namespace = "http://microsoft.biztalk.cat/10/helloworld")]
    public interface IHelloWorld
    {
        [OperationContract(Action = "SayHello", ReplyAction = "SayHello", ProtectionLevel = ProtectionLevel.None)]
        [FaultContract(typeof(CustomError), Action = "CustomErrorFault")]
        HelloWorldResponse SayHello(HelloWorldRequest request);
    }

    [ServiceBehavior(Name = "HelloWorld", Namespace = "http://microsoft.biztalk.cat/10/helloworld")]
    public class HelloWorld : IHelloWorld
    {
        [OperationBehavior]
        public HelloWorldResponse SayHello(HelloWorldRequest request)
        {
            if (request == null || string.IsNullOrEmpty(request.Name))
            {
                throw CreateFault(NameCannotBeNullOrEmpty);
            }
            return new HelloWorldResponse(string.Format(SayHelloFormat, request.Name));
        } 

         private FaultException<CustomError> CreateFault(string message)
        {
             ...
            return fault;
        }
    }
}

 

Untyped Faults

Untyped SOAP faults are those that do not require a service operation to be decorated with a FaultContractAttribute that specify a custom data contract class.

BizTalk Orchestrations and Typed Faults

BizTalk Server 2006 R2 and BizTalk Server 2009 allow handling typed fault contracts when consuming WCF services from within orchestrations. The steps necessary to implement this technique are clearly described in the  article “How to Handle Typed Fault Contracts in Orchestrations” on MSDN. Instead, WCF adapters actually do not support processing typed fault contract exceptions within orchestrations published as WCF services. However, untyped SOAP faults can always be returned by orchestrations or pipelines. For more information on this topic review the article “How to Throw Fault Exceptions from Orchestrations Published as WCF Services” on MSDN.

This constraint arises from how the Receive Handler of WCF Adapters is actually implemented. The WCF Receive Adapter instantiates a singleton instance of the BizTalkServiceInstance class for each WCF Receive Location. This class can be found in the Microsoft.BizTalk.Adapter.Wcf.Runtime.dll assembly. In particular, as you can see in the picture below, the BizTalkServiceInstance class is decorated with the attribute ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple).

Reflector1

 

Therefore, all the incoming requests towards a given WCF Receive Location are are managed by the same singleton object. When you enable a WCF Receive Location, the Adapter initializes and opens a dedicated instance of a ServiceHost-derived class (WebServiceHost), which dynamically builds the WCF runtime components within the in-process or isolated host process. This includes the channel stack, dispatcher, and generic service instance. Almost all of the WCF adapters can be hosted within the BizTalk service process itself – the only exception is WCF-CustomIsolated, which must be used in a BizTalk isolated host by design. Even the HTTP adapters can be hosted in-process now. The WCF Adapters build the generic service contracts shown in the table below. Each service contract implemented by the BizTalkServiceInstance covers a different scenario.

[ServiceContract(Namespace = "http://www.microsoft.com/biztalk/2006/r2/wcf-adapter")]
public interface IOneWayAsync
{
    // Methods
    [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")]
    IAsyncResult BeginOneWayMethod(Message message, AsyncCallback callback, object state);
    [OperationContract(IsOneWay = true, Action = "BizTalkSubmit")]
    void BizTalkSubmit(Message message);
    void EndOneWayMethod(IAsyncResult result);
}

[ServiceContract(Namespace = "http://www.microsoft.com/biztalk/2006/r2/wcf-adapter")]
public interface IOneWayAsyncTxn
{
    // Methods
    [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")]
    IAsyncResult BeginOneWayMethod(Message message, AsyncCallback callback, object state);
    [OperationContract(IsOneWay = true, Action = "BizTalkSubmit")]
    void BizTalkSubmit(Message message);
    void EndOneWayMethod(IAsyncResult result);
}

[ServiceContract(Namespace = "http://www.microsoft.com/biztalk/2006/r2/wcf-adapter")]
public interface ITwoWayAsync
{
    // Methods
    [OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")]
    IAsyncResult BeginTwoWayMethod(Message message, AsyncCallback callback, object state);
    [OperationContract(IsOneWay = false, Action = "BizTalkSubmit")]
    Message BizTalkSubmit(Message message);
    Message EndTwoWayMethod(IAsyncResult result);
}

[ServiceContract(Namespace = "http://www.microsoft.com/biztalk/2006/r2/wcf-adapter")]
public interface ITwoWayAsyncVoid
{
    // Methods
    [OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")]
    IAsyncResult BeginTwoWayMethod(Message message, AsyncCallback callback, object state);
    [OperationContract(IsOneWay = false, Action = "BizTalkSubmit")]
    void BizTalkSubmit(Message message);
    void EndTwoWayMethod(IAsyncResult result);
}

[ServiceContract(Namespace = "http://www.microsoft.com/biztalk/2006/r2/wcf-adapter")]
public interface ITwoWayAsyncVoidTxn
{
    // Methods
    [TransactionFlow(TransactionFlowOption.Mandatory), OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")]
    IAsyncResult BeginTwoWayMethod(Message message, AsyncCallback callback, object state);
    [TransactionFlow(TransactionFlowOption.Mandatory), OperationContract(IsOneWay = false, Action = "BizTalkSubmit")]
    void BizTalkSubmit(Message message);
    void EndTwoWayMethod(IAsyncResult result);
}

 

As you can easily see in the code above, all the service contracts implemented by the BizTalkServiceInstance Class are generic and asynchronous. In fact, one-way service operations are decorated with [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")] attribute, while request-response methods are decorated with the [OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")] attribute. Besides, they receive a generic inbound message of type System.ServiceModel.Channels.Message and eventually return a message of the same type. As a consequence, these methods can accept any request message and eventually process any reply message. However, since these operations are generic and untyped, they are not decorated by any FaultContractAttribute. As a consequence, WCF Receive Locations cannot return typed fault exceptions. Since every WCF Receive Location is implemented as an instance of the BizTalkServiceInstance and this class cannot be customized or inherited to expose typed service contracts, BizTalk Server does not natively support throwing typed fault exceptions within orchestrations published as WCF services or more in general throwing typed fault exceptions within a WCF Receive Location.

At this point a question arises: is there any workaround to throw typed fault exceptions within orchestrations published as WCF services? The answer, fortunately, is yes.

The Solution

The WCF-Custom and WCF-CustomIsolated Adapters provided by BizTalk Server 2006 R2 and BizTalk Server 2009 allow to define a custom WCF binding configuration to meet your precise communication needs. These adapters can be used to define custom WCF configurations for Send Ports or Receive Locations and extend their standard functionality using custom components as endpoint and service behaviors, message inspectors, custom bindings, binding elements, channels, etc. Therefore, I decided to create the following custom components:

  • Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.WsdlExportExtensions assembly: flattens and extends the WSDL generated by the BizTalk WCF Service Publishing Wizard to enable WCF Receive locations to expose typed soap faults.
  • Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.MessageInspector assembly: intercepts a reply message and when this latter is a fault, it creates and returns a typed fault message.

The following picture depicts the architecture of the proof of concept I implemented to test these components:

Diagram

  1. A WCF-CustomIsolated Request-Response Receive Location receives a new xml document from a client application.
  2. The XML disassembler component within the XMLTransmit pipeline promotes the MsgType context property. The Message Agent submits the incoming message to the MessageBox (BizTalkMsgBoxDb).
  3. The inbound request starts a new instance of the HelloWorld orchestration type.
  4. The orchestration checks the incoming HelloWorldRequest message, and if the Name element is null or empty, it creates and return a new fault message of type CustomError. Otherwise, the orchestration returns a reply message of type HelloWorldResponse.
  5. The HelloWorld orchestration publishes the reply message to the MessageBox (BizTalkMsgBoxDb).
  6. The response message is retrieved by the WCF-CustomIsolated Request-Response Receive Location.
  7. The reply  message is processed by the CustomErrorMessageInspector component. If the response is a fault message, it creates a new typed fault message using the configuration data set on the Receive Location.
  8. The reply message is returned to the client application.

The following picture shows the XML Schema which defines the CustomError typed fault.

CustomErrorXsd

 

The following figure shows the configuration of the WCF-CustomIsolated Request-Response Receive Location.

RLConfig01 RLConfig02
RLConfig03 RLConfig04

 

The Receive Location exposes an endpoint that uses the WsHttpBinding and make use of the serviceThrottling service behavior (see System.ServiceModel.Description.ServiceThrottlingBehavior for more information), a custom service behavior called errorHandler and a custom endpoint behavior called wsdlExport :

The custom service and endpoint behaviors must be configured in the machine.config as follows:

Machine.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    ...
  <system.serviceModel>
      ...
    <extensions>
      <behaviorExtensions>
          ...
        <add name="errorHandler" type="Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.ErrorHandler.CustomErrorBehaviorExtensionElement, 
Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.ErrorHandler, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=d7f63d8d08d8f3a2"
/> <add name="wsdlExport" type="Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.WsdlExportExtensions.WsdlExportBehaviorExtensionElement,
Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.WsdlExportExtensions, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=d7f63d8d08d8f3a2"
/> </behaviorExtensions> </extensions> </system.serviceModel> </configuration>

 

The Include exception detail in faults option must be set on the Messages tab to enable the WCF Receive Location to return the error detail in fault messages. Below you can see the code of the CustomErrorHandler component. The constructor retrieves configuration data from the Receive Port configuration, while the IErrorHandler.ProvideFault method allows, in case of error, to generate a typed fault exception using the configuration data.

CustomErrorHandler Class

namespace Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.ErrorHandler
{
    /// <summary>
    /// This class can be customized to create a message inspector.
    /// </summary>
    public class CustomErrorHandler : IErrorHandler
    {
        #region Private Constants
        private const string Source = "CustomErrorHandler";
        private const string SoapActionFormat = "[CustomErrorHandler]: SOAP Fault Action = [{0}]";
        private const string SoapFaultCodeFormat = "[CustomErrorHandler]: SOAP Fault Code = [{0}]";
        private const string SoapFaultReasonFormat = "[CustomErrorHandler]: SOAP Fault Reason = [{0}]";
        private const string SoapFaultDetailFormat = "[CustomErrorHandler]: SOAP Fault Detail:";
        private const string MessageTypeFormat = "{0}#{1}";
        private const string DefaultAction = "CustomError";
        private const string DefaultFaultCode = "CustomError";
        private const string DefaultFaultReason = "CustomError";
        #endregion

        #region Private Fields
        private bool enabled = true;
        private bool traceEnabled = false;
        private int maxBufferSize = 2097152;
        private string typedFaults = null;
        private Dictionary<string, CustomErrorFaultInfo> faults = new Dictionary<string, CustomErrorFaultInfo>();
        #endregion

        #region Public Constructors
        public CustomErrorHandler(bool enabled,
                                  bool traceEnabled,
                                  int maxBufferSize,
                                  string typedFaults)
        {
            this.enabled = enabled;
            this.traceEnabled = traceEnabled;
            this.maxBufferSize = maxBufferSize;
            this.typedFaults = typedFaults;

            try
            {
                if (!string.IsNullOrEmpty(typedFaults))
                {
                    TypedFaults faultData = SerializationHelper.XmlDeserialize(typedFaults, typeof(TypedFaults)) as TypedFaults;
                    if (faultData != null &&
                        faultData.FaultList != null &&
                        faultData.FaultList.Count > 0)
                    {
                        for (int i = 0; i < faultData.FaultList.Count; i++)
                        {
                            if (!string.IsNullOrEmpty(faultData.FaultList[i].Name))
                            {
                                faults.Add(string.Format(MessageTypeFormat, faultData.FaultList[i].Namespace, faultData.FaultList[i].Name ?? string.Empty),
                                           new CustomErrorFaultInfo(faultData.FaultList[i].Action, 
                                                                    faultData.FaultList[i].Code, 
                                                                    faultData.FaultList[i].Reason));
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Source, ex.Message, EventLogEntryType.Error);
            }
        }
        #endregion

        #region IErrorHandler Members

        public bool HandleError(Exception error)
        {
            return true;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            try
            {
                if (error != null)
                {
                    string action = null;
                    FaultCode faultCode = null;
                    FaultReason faultReason = null;
                    XPathNavigator details = null;

                    RetrieveErrorData(error.Message, out action, out faultCode, out faultReason);
                    XPathDocument document = new XPathDocument(new StringReader(error.Message));
                    details = document.CreateNavigator();
                    MessageFault messageFault = MessageFault.CreateFault(faultCode, faultReason, details, new CustomErrorSerializer());
                    fault = Message.CreateMessage(version, messageFault, action);
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(Source, ex.Message, EventLogEntryType.Error);
                throw ex;
            }
            return;
        }
        #endregion

        #region Private Methods
        private void RetrieveErrorData(string message, 
                                       out string action, 
                                       out FaultCode faultCode,
                                       out FaultReason faultReason)
        {
            action = DefaultAction;
            faultCode = new FaultCode(DefaultFaultCode);
            faultReason = new FaultReason(DefaultFaultReason);

            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            if (traceEnabled)
            {
                TraceHelper.WriteLine(SoapFaultDetailFormat);
                TraceHelper.WriteLine(message);
            }

            string rootElement = null;
            string targetNamespace = null;

            using (StringReader stringReader = new StringReader(message))
            {
                XmlReader xmlReader = null;
                try
                {
                    xmlReader = XmlReader.Create(stringReader);
                    bool ok = true;
                    while (xmlReader.Read() && ok)
                    {
                        if (xmlReader.NodeType == XmlNodeType.Element)
                        {
                            rootElement = xmlReader.LocalName;
                            targetNamespace = xmlReader.NamespaceURI;
                            ok = false;
                        }
                    }
                }
                catch (Exception)
                {
                }
                finally
                {
                    if (xmlReader != null)
                    {
                        xmlReader.Close();
                    }
                }
            }
            if (!string.IsNullOrEmpty(rootElement))
            {
                if (targetNamespace == null)
                {
                    targetNamespace = string.Empty;
                }
                string messageType = string.Format(MessageTypeFormat, targetNamespace, rootElement);
                if (faults != null &&
                    faults.ContainsKey(messageType))
                {
                    if (!string.IsNullOrEmpty(faults[messageType].Action))
                    {
                        action = faults[messageType].Action;
                    }
                    if (!string.IsNullOrEmpty(faults[messageType].Code))
                    {
                        faultCode = new FaultCode(faults[messageType].Code);
                    }
                    if (!string.IsNullOrEmpty(faults[messageType].Reason))
                    {
                        faultReason = new FaultReason(faults[messageType].Reason);
                    }
                }
                if (traceEnabled)
                {
                    TraceHelper.WriteLine(string.Format(SoapActionFormat, action));
                    TraceHelper.WriteLine(string.Format(SoapFaultCodeFormat, faultCode));
                    TraceHelper.WriteLine(string.Format(SoapFaultReasonFormat, faultReason));
                }
            }
        }
        #endregion
    }
}

 

The following table contains the XML snippet used as configuration data by the CustomErrorHandler component. As you can see, the convention I used allows defining multiple typed faults in a declarative way. At runtime the CustomErrorHandler component will read and store configuration data in a dictionary. In particular the value of the Name and Namespace elements of each TypedFault will be concatenated (Namespace#Name) to form the  message type of the corresponding error message. If the orchestration returns a typed fault, the CustomErrorHandler component will determine the message type of the typed fault (targetNamespace#RootElementName) and it will retrieve the corresponding information (Action, Code, and Reason) from the dictionary.

<TypedFaults xmlns="http://microsoft.biztalk.cat/10/typedfaults">
  <TypedFault>
    <Action>SayHello</Action>
    <Name>CustomError</Name>
    <Namespace>http://microsoft.biztalk.cat/10/customerror</Namespace>
    <Code>SayHello Error Code</Code>
    <Reason>SayHello Orchestration Error</Reason>
  </TypedFault>
</TypedFaults>

 

If you set the TraceEnabled property to true, at runtime the CustomErrorHandler component will produce a trace that you can intercept and review with a tool such as Debug as shown in the picture below.

DebugView

The WCF-Receive Location can be created using the BizTalk WCF Service Publishing Wizard. In particular,  this allows to create a WCF Receive Location to expose the HelloWorld orchestration as a WCF service. Moreover, setting the Enable metadata endpoint option it’s possible to enable the  WCF Receive Location to expose a Metadata Endpoint.

BizTalkWCFServicePublishingWizard

 

This allows a developer to use Visual Studio or a tool such as svcutil to generate a proxy class to invoke  the WCF Receive Location. Now, let’s say that you want to create a WCF client application to invoke the WCF Receive Location. Within Visual Studio you can create a new Windows Application, right-click the project inside the Solution Explorer and then select Add Service Reference. If the WCF Receive Location exposes a Metadata Endpoint, this operation will create a proxy class to invoke the corresponding WCF web service. However, if you review the code and in particular the contract interface, you’ll realize that the SayHello method is not decorated with a FaultContractAttribute. This is due to the fact, that since BizTalk Server does not support throwing typed fault exceptions, the native wsdl returned by the Metadata Endpoint exposed by the WCF Receive location does not contain any soap fault message. In order to sort out this problem, you can adopt one of the following techniques:

  • Create and expose a custom wsdl that contains the definition of your typed soap faults.
  • Create a custom endpoint behavior to dynamically  modify the wsdl produced by BizTalk.

The first technique is quite straightforward:

  • You manually define a custom wsdl using a text or xml editor.
  • You publish the resulting wsdl file to IIS (e.g. http://localhost/samples/service.wsdl)
  • You configure your WCF-Custom or WCF-CustomIsolated Receive Location to expose metadata and in particular the newly created wsdl file.

In order to accomplish the finale step, you can proceed as follows:

  • Start the BizTalk Server Administration Console.
  • Open your WCF-Custom/WCF-CustomIsolated Receive Location.
  • Click the Configure button in the Transport section.
  • Click the Behavior tab.
  • Right-click the Service Behavior node and choose to Add Extension.
  • In the Select Behavior Extension window, choose the serviceMetadata component.
  • Set the value of the httpGetEnabled property of the serviceMetadata behavior to True (see the picture below).
  • Set the value of the externalMetadataLocation property of the serviceMetadata behavior to the url of your hand-built wsdl (see the picture below).

serviceMetadata

 

The second technique consists in creating a custom component called WsdlExtensions to modify at run-time the wsdl returned by the the Metadata Endpoint exposed by a WCF-Custom/WCF-CustomIsolated Receive location. This component is implemented as an a custom endpoint behavior and it can be used along with any WCF-Custom or WCF-CustomIsolated Receive Location, as shown in the picture below.

WsdlExportTab

 

The WsdlExtensions property exposed by the component accepts an XML snippet that allows to specify how the wsdl has to be customized at runtime.

<WsdlExtensions xmlns="http://microsoft.biztalk.cat/10/wsdlextensions">
    <Prefix>bts</Prefix>
    <XmlSchemas>
        <XmlSchema>
            <Name>CustomError</Name>
            <Namespace>http://microsoft.biztalk.cat/10/customerror</Namespace>
        </XmlSchema>
    </XmlSchemas>
    <Messages>
        <Message>
            <Name>HelloWorld_SayHello_CustomErrorFault_FaultMessage</Name>
            <Namespace>http://microsoft.biztalk.cat/10/customerror</Namespace>
            <Parts>
                <Part>
                    <Name>detail</Name>
                    <Element>CustomError</Element>
                </Part>
            </Parts>
        </Message>
    </Messages>
    <PortTypes>
        <PortType>
            <Name>HelloWorld</Name>
            <Operations>
                <Operation>
                    <Name>SayHello</Name>
                    <Faults>
                        <Fault>
                            <Name>CustomErrorFault</Name>
                            <Message>HelloWorld_SayHello_CustomErrorFault_FaultMessage</Message>
                        </Fault>
                    </Faults>
                </Operation>
            </Operations>
        </PortType>
    </PortTypes>
</WsdlExtensions>

 

As shown in the picture above, the components allows to define the following information:

  • The prefix for the namespace of new messages created inside the wsdl.
  • One or multiple Xml Schemas each defining a different typed fault. These schemas must be deployed to BizTalk. At runtime, the component is able to retrieve the content of each schema from the BizTalkMgmtDb that  subsequently is inserted in the outbound wsdl.
  • One or multiple fault messages, each containing one or multiple parts.
  • One or multiple operation-fault associations. At runtime the component search through the original wsdl structure and creates faults accordingly.

At runtime the WsdlExtensions component validates the XML configuration specified on the port and if the TraceEnabled property is set to true, it produces a trace with a tool such as Debug as shown in the picture below.

DebugViewAddServiceReference

 

The following picture shows the wsdl produced by the WsdlExtensions component at runtime. In particular, the parts added by the component are highlighted by a red frame.

WSDL

Note: Flattening the wsdl  to include the XML schemas used to define messages allows to make the wsdl document compatible with non-Microsoft development environments. In fact, Visual Studio supports import and include directives in a wsdl, but some non-Microsoft development environments are not able to consume the wsdl exposed by a WCF web service.

Note: the XML Schemas defining the structure of the XML configuration data consumed at runtime respectively by the CustomErrorMessageInspector and by the WsdlExportEndpointBehavior can be found inside the BehaviorSchemas project. The corresponding assembly doesn’t need to be deployed to BizTalk Server.

Proof Of Concept in Action

In order to test the WCF custom components, I created a WinForms application. In particular, when I leave the Name textbox blank and press the Call button, the HelloWorld orchestration returns a fault message. The client application intercepts the error with a catch (FaultException<HelloWorldBizTalk.CustomError> ex) block and opens a MessageBox to show the detail of the exception.

ClientApplication

I created 3 different Receive Locations to test my ErrorHandler component with different WCF settings:

  • PublishTypedFaults.HelloWorld.WCF-CustomIsolated.NoSecurity.ReceiveLocation
    • Adapter: WCF-CustomIsolated
    • Binding: WsHttpBinding
    • Security Mode: None
    • ClientCredentialType: N/A
  • PublishTypedFaults.HelloWorld.WCF-CustomIsolated.TransportSecurity.ReceiveLocation
    • Adapter: WCF-CustomIsolated
    • Binding: WsHttpBinding
    • Security Mode: Transport
    • ClientCredentialType: Windows
  • PublishTypedFaults.HelloWorld.WCF-Custom.MessageSecurity.ReceiveLocation
    • Adapter: WCF-Custom
    • Binding: NetTcpBinding
    • Security Mode: Message
    • ClientCredentialType: Windows

 

Show me the code!

At this point, you are probably saying:

– ok, cool, where’s the code?

Well, you can find the code for BizTalk Server 2009 here, but its should work fine also on BizTalk Server 2006 R2. Obviously, in this latter case you need to readapt the projects for Visual Studio 2005.

Obviously, everything there are many ways to implement the same functionalities, so I’d be glad if you could leave a message on my blog and let me know how you used/customized/improved my code. Enjoy! 😉


Comments (29)

  1. Thank you for submitting this cool story – Trackback from DotNetShoutout

  2. Yogesh Mulye says:

    Excellent article and enough details there. Just what I had in my mind.

    Thank you.

  3. Vinoth says:

    Your sample code works fine with basicHttpBinding. But getting below error if I use the wsHttpBinding.Please advise

    The signature verification failed. Please see inner exception for fault details.

    Inner exception:

    Digest verification failed for Reference ‘#_0’.

  4. Paolo says:

    Hi Vinoth,

    could you please send me a repro of your test to better scope the problem? My demo works fine and it makes use of a WCF-CustomIsolated Receive Location to expose the orchestration as a WCF service and this Receive Location is configured to use the WsHttpBinding binding.  

  5. efung says:

    Hi,

    I have tried to deployed the solution for testing, but I get the following error when I called SayHello using the driver.

    "The message could not be processed. This is most likely because the action ‘SayHello’ is incorrect or because the message contains an invalid or expired security context token or because there is a mismatch between bindings. The security context token would be invalid if the service aborted the channel due to inactivity. To prevent the service from aborting idle sessions prematurely increase the Receive timeout on the service endpoint’s binding."

    I think I have missing something on configuration.  Do you have any idea on that?

    Thanks,

    Efung

  6. leprino says:

    Hi Efung,

    I doubt that the problem is due to the Action, as the operation exposed by the WCF Receive Location accepts any Action as shown in te code snippet below (Action="*"):

    [OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")]

       IAsyncResult BeginTwoWayMethod(Message message, AsyncCallback callback, object state);

    The next check is to verify that the binding used on the client side and server side (WCF Receive Location). Since you downloaded my code (that works fine on my machine, and several other people were able to successfully install and use the demo) I’m inclined to think that bindings are ok.

    So the next question is: are you hosting the WCF Receive Location within IIS 6.0 or 7.0? What kind of Application Pool (Integrated/Classic) are you using to host the WCF Receive Location? What kind of security did you set on the web application hosting the WCF Receive Location? Take into account that since the WCF Receive Location is using the wsHttpBinding with no security (no authentication), you have to enable Anonymous Authentication on the web application hosting the WCF Receive Location.

    Ciao,

    Paolo

  7. WK says:

    Hi Paolo,

    Thanks for your work on typed fault handling in biztalk.

    If I would like to change to use my own typed fault schema, instead of customerrors, how should I proceed?

    Thanks.

    WK

  8. leprino says:

    Hi WK,

    if you analyze my code, you’ll find that the MessageInspector library doesn’t have any dependency on the Xml Schema of the CustomError class. Therefore, you can use any Typed Fault class, included your own class, in place of the CustomError. In this case, it’s important to declare the following informations in the xml snippet used to configure the CustomErrorEndpointBehavior:

    <TypedFaults xmlns="http://microsoft.biztalk.cat/10/typedfaults"&gt;

       <TypedFault>

           <Action>Action of the Type Fault message</Action>

           <Name>The name of the Root Element of the Type Fault</Name>

           <Namespace>The targetNamespace of the Root Element</Namespace>

           <Reason>A custom error message, if you want to customize this latter on the Receive Location.</Reason>

       </TypedFault>

    </TypedFaults>

    you can even configure use the CustomErrorEndpointBehavior without specifying a value for its TypedFaults property, but in this case you cannot control the Action of the Fault message or error description. A default Action and Reason will be created for you by the component. At this regard, I suggest you to review the code of the BeforeSendReply method of the CustomErrorMessageInspector class and the constructor of the CustomErrorMessageFault class. Let me know if you managed to use your own typed fault schema with the CustomErrorEndpointBehavior. 😉

    Ciao,

    Paolo

  9. WK says:

    Thanks for your info in previous conversation.

    I have another question, is this prototype working fine when we choose wshttp and nettcp, with security mode is not set to "None"?

    I found that it shows error as "Digest verification failed for Reference ‘#_0’."

    What information you would like to get for this issue?

    Thanks.

  10. leprino says:

    HI WK,

    unfortunately I’m quite busy in this period. However, I’ll examine the problem you highlighted and follow up as soon as I’ll have some spare time.

    Ciao,

    Paolo

  11. leprino says:

    Hi WK,

    I extended my sample as follows:

    – I created another WCF-CustomIsolated Receive Location that uses the wsHttpBinding with the Security Mode = Transport.

    – I changed the client application, so now you can decide if you want to invoke the orchestration via the RL via Security Mode = None (BizTalk option) or via the RL with Security Mode = Transport (BizTalkSSL option).

    Everything worked as expected and I didn’t see the error you mentioned.

    Moreover, I tried to access the WSDL generated by the new WCF-CustomIsolated RL with Security Mode = Transport, and one again the results were as expected.

    For your convenience, I updated the zip file (only for BizTalk Server 2009) containing the up-to-date code and bindings. Let me know if this has been of any help. 🙂

    Ciao,

    Paolo

  12. WK says:

    Hi Paolo,

          In our case, we are using nettcp binding with security Mode = Message. The message client credential type = "Windows".

          As mentioned in previous message, a Communication Exception is caught in the client as "Digest verification failed for Reference ‘#_0’.

          Would you mind helping to simulate our case in your environment?

          Thanks again.

    Rgds,

    WK

  13. leprino says:

    Hi WK, I experienced the same problem when using the following configuration:

    Adapter: WCF-Custom

    Binding: NetTcpBinding

    Security: Message

    ClientCredentialType: Windows

    so I made some researches on MSDN and I finally decided to change approach. I replaced the custom endpoint behavior with a custom service behavior and I replaced the MessageInspector with a custom class which implements the IErrorHandler interface, which is more appropriate to create a custom typed fault.

    I successfully tested the new component using 3 different Receive Locations:

    PublishTypedFaults.HelloWorld.WCF-CustomIsolated.NoSecurity.ReceiveLocation

    – Adapter: WCF-CustomIsolated

    – Binding: WsHttpBinding

    – Security Mode: None

    – ClientCredentialType: N/A

    PublishTypedFaults.HelloWorld.WCF-CustomIsolated.TransportSecurity.ReceiveLocation

    – Adapter: WCF-CustomIsolated

    – Binding: WsHttpBinding

    – Security Mode: Transport

    – ClientCredentialType: Windows

    PublishTypedFaults.HelloWorld.WCF-Custom.MessageSecurity.ReceiveLocation

    – Adapter: WCF-Custom

    – Binding: NetTcpBinding

    – Security Mode: Message

    – ClientCredentialType: Windows

    I refreshed the content of the this post to reflect the new implementation, so in order to use the new component, you need to read through the article, properly register the FQDN of the assembly in the machine.config, and finally reconfigure your Receive Location to use the new custom service behavior instead of the previous custom endpoint behavior. Let me know if the new component solves your problem.

    Ciao,

    Paolo

  14. WK says:

    Hi Paolo,

         First of all, thank you very much for your quick response.

         We have implemented the solution you provided in our environment. But unfortunately, we cannot get the expected result.

    The detail is:

    1. In CustomErrorHandler.cs, we debug the following code.

           public void ProvideFault(Exception error, MessageVersion version, ref Message fault)

           {

               try

               {

                   if (error != null)

                   {

                       string action = null;

                       FaultCode faultCode = null;

                       FaultReason faultReason = null;

                       XPathNavigator details = null;

                       RetrieveErrorData(error.Message, out action, out faultCode, out faultReason);

                       XPathDocument document = new XPathDocument(new StringReader(error.Message));

                       details = document.CreateNavigator();

                       MessageFault messageFault = MessageFault.CreateFault(faultCode, faultReason, details, new CustomErrorSerializer());

                       fault = Message.CreateMessage(version, messageFault, action);

                   }

               }

               catch (Exception ex)

               {

                   EventLog.WriteEntry(Source, ex.Message, EventLogEntryType.Error);

                   throw ex;

               }

               return;

           }

    The fault message we captured is:

    <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing&quot; xmlns:s="http://www.w3.org/2003/05/soap-envelope"&gt;

     <s:Header>

       <a:Action s:mustUnderstand="1">CustomError</a:Action>

     </s:Header>

     <s:Body>

       <s:Fault>

         <s:Code>

           <s:Value>s:CustomError</s:Value>

         </s:Code>

         <s:Reason>

           <s:Text xml:lang="en-US">CustomError</s:Text>

         </s:Reason>

         <s:Detail>

           <ns0:FaultMessage xmlns:ns0="http://testingschemas"&gt;

             <ns0:Reply>

               <ns0:ReplyCode>FaultCode_0</ns0:ReplyCode>

               <ns0:Error>Error_0</ns0:Error>

               <ns0:ID>ID_0</ns0:ID>

             </ns0:Reply>

           </ns0:FaultMessage>

         </s:Detail>

       </s:Fault>

     </s:Body>

    </s:Envelope>

    The content inside the node <s:Detail></s:Detail> is our typedfault.

    2. The service behaviour extension "errorHandler" is empty.

    3. The endpoint behaviour extension "wsdlexport" is

    <WsdlExtensions xmlns="http://microsoft.biztalk.cat/10/wsdlextensions"&gt;

    <Prefix>q2</Prefix>

    <XmlSchemas>

    <XmlSchema>

    <Name>FaultMessageType</Name>

    <Namespace>http://testingschemas</Namespace&gt;

    </XmlSchema>

    </XmlSchemas>

    <Messages>

    <Message>

    <Name>InitializingEvent_FaultMessage</Name>

    <Namespace>http://testingschemas</Namespace&gt;

    <Parts>

    <Part>

    <Name>detail</Name>

    <Element>FaultMessage</Element>

    </Part>

    </Parts>

    </Message>

    </Messages>

    <PortTypes>

    <PortType>

    <Name>ProcessEventPort</Name>

    <Operations>

    <Operation>

    <Name>InitializingEvent</Name>

    <Faults>

    <Fault>

    <Name>Fault</Name>

    <Message>InitializingEvent_FaultMessage</Message>

    </Fault>

    </Faults>

    </Operation>

    </Operations>

    </PortType>

    </PortTypes>

    </WsdlExtensions>

    4. In previous version, we can capture the typedfault "FaultMessageType" in the client catch exception

                       catch (FaultException<TEST.FaultMessageType> ex)

                       {

                       }

        But in current version, we can only capture the general fault exception in the client catch exception

                       catch (FaultException ex)

                       {

                       }

        The fault exception type we capture is "CustomError"

    Do you have any idea?

           Thanks.

    Rgds,

    WK

  15. leprino says:

    Hi WK,

    the reason is probably because the proxy is not able to recognize the Action of the typed fault. I don’t think that the Action of your TEST.FaultMessageType is CustomError, right?

    In any case, to find out the expected Action and fix the problem, you can proceed as follows:

    1) Find the service contract implemented by your proxy class.

    2) Take note of the value of the Action property exposed by the FaultContractAttribute attribute. In the example below the Action value is SayHello:

    [System.ServiceModel.FaultContractAttribute(typeof(Microsoft.BizTalk.CAT.Samples.PublishTypedFaults.Driver.HelloWorldBizTalk.CustomError), Action="SayHello", Name="CustomError", Namespace="http://microsoft.biztalk.cat/10/customerror&quot;)]

    3) Open your WCF Receive Location configuration and make sure to specify the right Action and eventuallu a custom Reason and  Code in the XML snippet used to initialize the TypedFaults property of the errorHandler component:

    <TypedFaults xmlns="http://microsoft.biztalk.cat/10/typedfaults"&gt;

     <TypedFault>

       <Action>SayHello</Action>

       <Name>CustomError</Name>

    <Namespace>http://microsoft.biztalk.cat/10/customerror</Namespace&gt;

       <Code>SayHello Error Code</Code>

       <Reason>SayHello Orchestration Error</Reason>

     </TypedFault>

    </TypedFaults>

    My component at runtime will use this information to properly set the Action header and the Fault Code and Reason within the body.

    Please try my suggestion and let me know. 🙂

    Ciao,

    Paolo

  16. Mark says:

    Hi Paolo,

    Great article. Having trouble getting the code. The zip archive seems to be invalid.

    Cheers

    Mark

  17. leprino says:

    Hi Mark,

    thanks for the feedback. I published a new copy of the code on my SkyDrive. Download it and try to unzip it and let me know if this time it works. 😉

    Ciao,

    Paolo

  18. Mark says:

    Hi Paolo,

    Still getting the same error I'm afraid. Error states "No CE signature found"

    Regards

    Mark

  19. leprino says:

    Hi Mark,

    write directly to my email address (paolos@microsoft.com) and I'll try to send you the code in another format (e.g. rar). Ok?

    Ciao,

    Paolo

  20. U3 says:

    Dear Paolo, thank you for your great work on this solution.

    i wonder i you could answer my questions below;

    1- from where should i start in order to write my own error ?

    2- should I use the wsdlexport for client behavior, since, we are using a wcf-custom (in-process hosting) and we don't need the metadata option ?

  21. leprino says:

    Hi mate,

    The best way to create your own error is probably customizing the Xml Schema of the CustomError message contained in the TypedFaults project and deploy the assembly. When you have done that, you can simply use this schema within your orchestrations to generate fault messages, as in my example. At this regard, remember that you have to right-click the operation within the Logical Request-Response Port inside the orchestration and choose the New Fault Message item from the context menu to generate a new Fault Message that you can use to raise a typed fault which structure is identified by the XML schema.

    Then, you have to properly declare the name and namespace of your custom error schema within the XmlSchemas element of the xml configuration contained in the WsdlExtension property of the wsdlExport endpoint behavior. At runtime, the behavior will read the schema from the BizTalkMgmtDb and include it in the resulting WSDL.

    Last but not least, you need to properly "attach" the custom error to the operation exposed by your WCF-Custom Receive Location, as explained in my article. In other words, you have to declare a new fault message within the Messages element of the xml configuration used by the wsdlExport endpoint behavior and specify that message as fault for the operation in question.

    Regarding your second question, if you don't use the wsdlExport endpoint behavior on your WCF-Custom Receive location, the corresponding WSDL will not be adequately extended with soap faults. Therefore, when you generate a client proxy using Visual Studio "Add Service Reference…" functionality or the svcutil tool, the service contract will not contain FaultContracts and at runtime will not be able to handle typed faults. You can eventually configure the wsdlExport endpoint behavior on your WCF-Custom Receive Location just when you need to generate the client proxy, and then, when you have done this, you eliminate that. Let me know if I answered your questions!

    Ciao,

    Paolo

  22. Kirby says:

    Hi Paolo,

    is this still the way to go in BTS 2010? Does it even work in BTS 2010?

    Ciao

    Kirby

  23. leprino says:

    Hi Kirby,

    as far as know, nothing changed in BizTalk Server 2010 that justifies another approach. WCF Adapter have not been changed or extended. The code should be ok for BizTalk Server 2010 as well, should you encounter any problems recompiling assemblies against 4.0 .NET Frameowrk in in Visual Studio 2010, just let me know!

    Ciao,

    Paolo

  24. Ken says:

    Dear Paolo,

    I tried with your code and it works fine! But I got a question and would like to know if you have any idea.

    I now want to have 2 operation in one port and I would like to have typed fault for both action. I found that the errorHandler could only handle either one of the operation. When I try to put 2 typefault there, it seems couldn't work. I wonder if that is a limitation?

    Thanks very much.

    Ken

  25. leprino says:

    Hi Ken,

    thanks for the feedback. When I developed the component I assumed the WCF receive location was explosing a single operation, so I never tried the scenario you described. Therefore, probebly the issue you describe is probably a limitation of my component. 😉

    Ciao,

    Paolo

  26. Ken says:

    Dear Paolo,

    Thanks for your comment. Lately I started to pick up the issue again and wonder if we could modify the component to make it capable to return 2 different typefault in one single port. Paolo, do you have any idea on what I should first take a look in and where most likely I will need to do customization? I am not so sure where to start of.

    Thanks very much for your time.

    Ken

  27. leprino says:

    Hi Ken,

    indeed the answer should be divided in 2 parts:

    – returning one or multiple typed faults from a single port or operation.

    – modifying the WSDL to declare that a certain operation can return one or multiple typed faults.

    The CustomErrorHandler component covers the first part of the equation, whereas the WsdlExportEndpointBehavior component covers the second part.

    In theory, returning multiple typed faults from a single operation should be possible by changing the configuration of the above components,

    without changing their code, but to confirm my assumption, you should try out the following approach.

    Let's say that your application can throw 2 different types of typed faults identified by the following information.

    – name and root element=ValidationError, namespace=http://www.myapplication.com/…/errormessages

    – name and root element=BackEndSystemError, namespace=http://www.myapplication.com/…/errormessages

    Let's assume that these 2 errors are defined by 2 XML schemas contained in a deployed BizTalk assembly called CustomErrors with targetNamespace equal to http://www.myapplication.com/…/errormessages.

    Under this condition, you can simply assign the following XML document to the TypedFaults property of the errorHandler service behavior:

    <TypedFaults xmlns="microsoft.biztalk.cat/…/typedfaults">

       <TypedFault>

           <Action>ValidationErrorAction</Action>

           <Name>ValidationError</Name>

           <Namespace>http://www.myapplication.com/…/Namespace&gt;

           <Code>ValidationErrorCode</Code>

           <Reason>ValidationErrorReason</Reason>

       </TypedFault>

       <TypedFault>

           <Action>BackEndSystemErrorAction</Action>

           <Name>BackEndSystemError</Name>

           <Namespace>http://www.myapplication.com/…/Namespace&gt;

           <Code>BackEndSystemErrorCode</Code>

           <Reason>BackEndSystemErrorReason</Reason>

       </TypedFault>

    </TypedFaults>

    Let's assume that your application exposes a single WCF receive location called OrderReceiveLocation with 1 operation called ProcessOrder.

    To declare that the latter can throw both the ValidationError and BackEndSystemError typed faults, you should assign the

    following XML to the WsdlExtensions property of the wsdlExport endpoint behavior:

    <WsdlExtensions xmlns="microsoft.biztalk.cat/…/wsdlextensions">

       <Prefix>bts</Prefix>

       <XmlSchemas>

           <XmlSchema>

               <Name>ValidationErrors</Name>

               <Namespace>http://www.myapplication.com/…/Namespace&gt;

           </XmlSchema>

       </XmlSchemas>

       <Messages>

           <Message>

               <Name>OrderReceiveLocation_ProcessOrder_ValidationErrorFault_FaultMessage</Name>

               <Namespacehttp://www.myapplication.com/…/Namespace&gt;

               <Parts>

                   <Part>

                       <Name>detail</Name>

                       <Element>ValidationError</Element>

                   </Part>

               </Parts>

           </Message>

            <Message>

               <Name>OrderReceiveLocation_ProcessOrder_BackEndSystemErrorFault_FaultMessage</Name>

               <Namespacehttp://www.myapplication.com/…/Namespace&gt;

               <Parts>

                   <Part>

                       <Name>detail</Name>

                       <Element>BackEndSystemError</Element>

                   </Part>

               </Parts>

           </Message>

       </Messages>

       <PortTypes>

           <PortType>

               <Name>OrderReceiveLocation</Name>

               <Operations>

                   <Operation>

                       <Name>ProcessOrder</Name>

                       <Faults>

                           <Fault>

                               <Name>ValidationErrorFault</Name>

                               <Message>OrderReceiveLocation_ProcessOrder_ValidationErrorFault_FaultMessage</Message>

                           </Fault>

                           <Fault>

                               <Name>BackEndSystemErrorFault</Name>

                               <Message>OrderReceiveLocation_ProcessOrder_BackEndSystemErrorFault_FaultMessage</Message>

                           </Fault>

                       </Faults>

                   </Operation>

               </Operations>

           </PortType>

       </PortTypes>

    </WsdlExtensions>

    I didn't make any tests, so you should validate my assumptions. In this case, please, send me a feedback! 😉

    Ciao,

    Paolo

  28. Ken says:

    Dear Paolo,

    Thanks for your advice! I need sometime to read it all. Thanks very much!!

    Ken