Pages

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

Tuesday, September 6, 2011

source : paulo salvatori's Blog

 

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 theBizTalkServiceInstance 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 typeSystem.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 typeCustomError. 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.
RLConfig01RLConfig02
RLConfig03RLConfig04

The Receive Location exposes an endpoint that uses the WsHttpBinding and make use of the serviceThrottling service behavior (seeSystem.ServiceModel.Description.ServiceThrottlingBehavior for more information), a custom service behavior called errorHandler and a custom endpoint behavior calledwsdlExport :
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 theIErrorHandler.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 asDebug 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 theHelloWorld 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 aFaultContractAttribute. 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-Customor 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 theWsdlExportEndpointBehavior 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, theHelloWorld 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! ;-)
Read more ...

AS2 Party Biztalk Encryption on Dynamic Ports using Certificates

Tuesday, September 6, 2011

Q:
Please consider this scenario:
Local (BT)Remote As2 Server (mendelson)
FromTo
Party_AParty_B
  1. Many AS2 relationships.
  2. same account for all of the hosts.
  3. Certificates on this account:
  • Current User\Personal and Trusted root certificates
  • keypair_A
  • Local computer \Other People and Trusted root certificates
  • public_key_B

No certificates have been configured to override the group, nor at the port level (dynamic port). No host has an encryption certificate attached to it.

Test 1: Send a Message from party_B  to Party_A (encrypted and signed). No certificate set for the BizTalk group.
Error: "The Signing Certificate has not been configured for AS2 party.  AS2-From: B AS2-To: A "

Test 2: Same as Test 1, but configuring a certificate for the BizTalk group.
Success. 
But this is a problem, because I need to used other signatures for other relationships! (Say Party_C/Party_D)

Test 3: Send a Message from party_A  to Party_B. Certificate set for the BizTalk group.
Error: "The Encryption Certificate has not been configured for AS2 party.  AS2-From: A AS2-To: B "

Test 4: Same as Test 3, but configuring the public certificate for the Party_B party (not for the profile/signature)
Error: "The Encryption Certificate has not been configured for AS2 party.  AS2-From: A AS2-To: B"
So it doesn't make a difference to setup the certificate at the party level.

Test 5: Same as Test 4, except that the certificate thumbprint was configured at the send port level.
Success
But this shows a high limitation in BizTalk, since a port should be reused by many relationships/parties. I am using a dynamic port, why would I attach an encryption certificate to it while it makes more sense to attach the certificate to the party/relationship? Since the only way to specify the encryption certificate is at the port level, then I would be forced to give up using a dynamic port and instead I'd have to use as many static ports as as2 relationships are in my system. And there can be a lot. This is a maintenance burden.
Up to here this could be enough if I didn’t need to configure other AS2 relationships, and some of them require us to use different signatures. Basically we need to give each partner a brand new signature and ecryption certificate (key pair) that represents us, while we receive their public .cer file.
I also wanted to test the signing certificate override feature:
Test 6: Same as Test 1. Send a Message from party_B  to Party_A (encrypted and signed). No certificate set for the BizTalk group, but instead configured in the party`s profile AS2 settings
Error: "The Signing Certificate has not been configured for AS2 party.  AS2-From: B AS2-To: A "
This means that the overriding did not occur!. OK then, now lets configure a different signature certificate at the biztalk group, perhaps that will make BT happy and let the overriding occur:
Test 7: Same as Test 6. but configuring a different signature, expecting BT to take the one at the As2 properties level of the party (the correct one)
Error: Success on BizTalk side but the AS2 mendelson server complains about the MDN having a signature error, meaning: Verification of digital signature failed (Verification failed)
This means that BizTalk refuses to use the certificate that I want to use (the one at the As2 porperties level) but instead keeps using the one defined at the group level.

So to summarize I am having two issues:
Biztalk forces to use encryption  certificate on the port level, which defeats the purpose of providing the good functionality of dynamic ports. Can encryption certificates be configured dynamically for dynamic ports? (other than programmatically  setting BTS.EncryptionCert which doesnt work)
As for the signatures, I am not able to have Biztalk use signature per party/relationship and keeps using the one defined at the group level. If no certificate defined at the group level then it doesnt matter other configurations, there'd be an error. If the certificate is set, then it can use only that one certificate for signature, regardless of overriding settings.
Any ideas are much appreciated,

Thanks!


Sol:

For the issue of dynamic ports and certificates I got the following from Microsoft:
"Dynamic send ports in BizTalk allow one to specify the destination URL at runtime rather than at design time. Everything else needs to be configured statically. When it comes to picking the appropriate certificate at runtime, dynamic ports can’t help. As for your scenario, we do understand that it is important to minimize the overhead in terms of number of pipelines/ports to be managed. Ideally the dynamic ports  should have been somewhat  flexible in terms of which settings do they allow to be provided dynamically at runtime (currently it is possible for only send URL and everything else must be configured statically). However, there is no such functionality that BizTalk provides as of now. I would suggest you to raise this as a new feature request which will have to go through the SE route (mostly as a DCR – design change request). "
Alright, I went back to the static approach.
Still pending the second issue (BizTalk forces using one single signature certificate for all outbound transmissions, regardless of overriding settings).


For the signatures-per-party issue, it is finally working.

If BT has two parties configured (A=Local, B=Remote), and an agreement( A->B, B->A) the problem was that the signature certificate was being configured on the A->B one way agreement.

When B sent an As2 to A, it was expecting an MDN signed by A, so the tab A->B was the obvious place to set the certificate the A uses to sign. Also, according to http://msdn.microsoft.com/en-us/library/ff629741(BTS.70).aspx, "

Select the Override group signing certificate check box to use the certificate provided in this page for signing outgoing AS2 messages and MDN.

", meaning, the certificate is for outgoing messages from A to B. When set on the oposite tab (B->A) it started working fine.
Ended up using the same signing certificate on both tabs of the agreement. Note that in a multiserver environment changes are not taken immediately. Need to restart the entire server for changes to really be there.



 

Read more ...

Generating Serialized Classes From Xsd.exe with XSD Schema Imports

Thursday, September 1, 2011
Q:

Hi  Guys I'm Facing problem generating Serialized classes from schema which has multiple imports in my canonical schema

It worked previously when i generated for a individual schema without any imports ?.
can you help me on this Im getting Error : Error Generating Classes for xxx.schema

Sol:

Hi there,

You need to add the Import Schema's along with  the Schema for which you are generating Serialized Classes You need to Add the import Schemas/Referenced Schemas Also while generating the serialized Classes


Below is an example:
Generating Serialized Classes Using XSD.EXE
C:\Program Files (x86)\Microsoft Visual Studio 8\VC>
xsd  C:\AOP\ISIS\Common\Schemas\ISIS.Common.Schemas.AdditionalInfo\AdditionalInfo.xsd C:\AOP\ISIS\Common\Schemas\ISIS.Common.Schemas.MessageHeader\MessageHeader.xsd C:\AOP\ISIS\Common\Schemas\ISIS.Common.Schemas.TransactionResult\TransactionResult.xsd C:\AOP\CIS\Common\Schemas\CIS.Common.Schemas.ActivateParkedDevice\ActivateParkedDeviceResponse.xs
d /l:cs /n:CIS.Common.Schemas.ActivateParkedDevice.Helper /outputdir:C:\AOP\CIS\
Common\Schemas\CIS.Common.Schemas.ActivateParkedDevice.Helper /c
 
After Execute : Result:

Microsoft (R) Xml Schemas/DataTypes support utility[Microsoft (R) .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation. All rights reserved.Writing file 'C:\AOP\CIS\Common\Schemas\CIS.Common.Schemas.ActivateParkedDevice.
Helper\ActivateParkedDeviceResponse.cs'.


Here
i was generating the generating the Serialized class for Activate Parked device response Schema which had XSD imports for Additional Info Schema and Transaction result Schema
 you need to add the reference as specified as Reference Schema's , Actual Schema , Generating Parameters.

Note: Sometimes if you are generating Multiple Schema's with the same imports under the same namespace  you will need to generate the Serialized classes the same way for both but the Generated Serialized Class may not be valid as there will be duplicate methods with in the same namespace
"You Need to remove the Redundant Classes Generated with same Name  or Generate Schema's under different Namespaces"
Read more ...