BizTalk: Behandlung von SOAP Fault Nachrichten in custom pipeline Komponenten
Heute habe ich für eine unserer BizTalk Send Pipeline Komponente ein Handling für SOAP Fault Nachrichten vorgesehen. Alle Recherchen, die ich im Internet gefunden habe beziehen sich auf WebServices und WCF Adapter, die solch eine Meldung "von außen" bekommen. Mein unten aufgeführter Helfer kann höchstwahrscheinlich nicht mit SOAP Faults umgehen, die nicht von BizTalk erzeugt wurden.
Mein Helfer behandelt ein anderes Szenario. Ich wollte die SOAP Fault Nachrichten behandeln, die von BizTalk erzeugt worden sind (in aller Regel
Routing Failures oder
missing correlation token oder
disposed service handler). Die von BizTalk erzeugten SOAP Fault Nachrichten verfügen über
Promoted Properties im Kontext, auf die man bequem zurückgreifen kann.
Ich habe dann für die Behandlung dieser SOAP Fault Nachrichten
einen kleinen Helfer geschrieben:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
// benötigte Referenzen (aus dem BizTalk SDK):
// Microsoft.BizTalk.GlobalPropertySchemas.dll
// Microsoft.BizTalk.Pipeline.dll
// Microsoft.XLANGs.BaseTypes
using Microsoft.BizTalk.Message.Interop;
using Microsoft.XLANGs.BaseTypes;
namespace MyProject.PipelineComponentHelper
{
/// <summary>
/// Helferklasse, die Methoden und Eigenschaften anbietet um mit BizTalk SOAP Fault
/// Nachrichten umgehen zu können.
/// </summary>
public class SoapFaultMessage
{
#region Constants
/// <summary>
/// Definiert den Typ einer SOAP Fault Nachricht.
/// </summary>
private const string c_ACKTYPEFAULT = "NACK";
/// <summary>
/// XPath Ausdruck, der auf den SOAP Fault Basisknoten zeigt.
/// </summary>
private const string c_XPATHSOAPFAULTBASE = "*[local-name='Body']/*[local-name='Fault']";
#endregion
#region Variable definitions
/// <summary>
/// Enthält die Referenz auf die AckType Property aus dem BizTalk SDK.
/// </summary>
private static readonly PropertyBase m_AckTypeMessageProperty = new BTS.AckType();
/// <summary>
/// Enthält die Referenz auf die AckDescription Property aus dem BizTalk SDK.
/// </summary>
private static readonly PropertyBase m_AckDescriptionMessageProperty = new BTS.AckDescription();
/// <summary>
/// Enthält die Referenz auf die AckFailureCode Property aus dem BizTalk SDK.
/// </summary>
private static readonly PropertyBase m_AckFailureCodeMessageProperty = new BTS.AckFailureCode();
/// <summary>
/// Enthält die BizTalk Nachricht.
/// </summary>
private IBaseMessage m_BizTalkMessage = null;
/// <summary>
/// Enthält den FailureCode der SOAP Fault Nachricht.
/// </summary>
private string m_FailureCode = string.Empty;
/// <summary>
/// Enthält die Fehlerbeschreibung der SOAP Fault Nachricht.
/// </summary>
private string m_FailureDescription = string.Empty;
/// <summary>
/// Enthält die SOAP Fault Nachricht als Xml Dokument.
/// </summary>
private XmlDocument m_MessageBody = null;
#endregion
#region Constructor
/// <summary>
/// Standard Konstruktor, der eine IBase Nachricht entgegennimmt.
/// </summary>
public SoapFaultMessage(IBaseMessage soapFaultMessage)
{
// Eine Exception werfen, wenn es sich um keine SOAP Fault Nachricht handelt.
if (!IsSoapFaultMessage(soapFaultMessage))
{
throw new ArgumentException("Es wurde versucht ein SOAPFaultMessage Objekt aus einer Nachricht zu erzeugen, die keine SOAP Fault Nachricht ist.");
}
// Datenübernahme
m_BizTalkMessage = soapFaultMessage;
}
#endregion
#region Public properties
/// <summary>
/// Liefert den SOAP Fault Fehlercode.
/// </summary>
public string FailureCode
{
get { return m_FailureCode; }
}
/// <summary>
/// Liefert die SOAP Fault Fehlerbeschreibung.
/// </summary>
public string FailureDescription
{
get { return m_FailureDescription; }
}
/// <summary>
/// Liefert die komplette Nachricht als Xml Dokument
/// </summary>
/// <remarks>Die Eigenschaft liest den Nachrichten Inhalt aus dem BizTalk Nachrichten
/// Body Stream. Sollte dieser nicht in der Lage sein durchsuchbar zu sein (CanSeek == false),
/// so ist der Stream nach dem Zugriff auf die Eigenschaft vollständig gelesen.</remarks>
public XmlNode SoapFaultBaseNode
{
get
{
return GetSoapFaultBaseNode();
}
}
#endregion
#region Public static methods
/// <summary>
/// Diese Methode überprüft, ob es sich bei der BizTalk Nachricht um eine SOAP Fault
/// Nachricht handelt. Als Kriterium dient die Kontexteigenschaft "AckType", die
/// vorhanden und "NACK" sein muss.
/// </summary>
/// <param name="msgToCheck">Die Nachricht, die es zu überprüfen gilt.</param>
/// <returns>True, falls es sich um eine SOAP Fault Nachricht handelt, ansonsten False.</returns>
public static bool IsSoapFaultMessage(IBaseMessage msgToCheck)
{
object ackType = null;
bool retVal = false;
string tmpString = string.Empty;
// Wenn kein Nachrichtenobjekt übergeben wurde, dann gleich False zurückgeben
if (msgToCheck != null)
{
// Kontexteigenschaft "AckType" auslesen
ackType = msgToCheck.Context.Read(m_AckTypeMessageProperty.Name.Name, m_AckTypeMessageProperty.Name.Namespace);
// Überprüfen, ob Kontexteigenschaften gefunden wurde, und ob es sich um eine SOAP Fault Nachricht handelt
if (ackType != null)
{
tmpString = (string)ackType;
retVal = tmpString.Equals(c_ACKTYPEFAULT);
}
}
return retVal;
}
#endregion
#region Private methods
/// <summary>
/// Extrahiert alle Informationen aus der SOAP Fault Nachricht.
/// </summary>
private void ExtractSoapInfoFromMessage()
{
object tmpObject = null;
// Fehlercode auslesen
tmpObject = m_BizTalkMessage.Context.Read(m_AckFailureCodeMessageProperty.Name.Name, m_AckFailureCodeMessageProperty.Name.Namespace);
if (tmpObject != null)
{
m_FailureCode = (string)tmpObject;
}
// Fehlerbeschreibung auslesen
tmpObject = m_BizTalkMessage.Context.Read(m_AckDescriptionMessageProperty.Name.Name, m_AckDescriptionMessageProperty.Name.Namespace);
if (tmpObject != null)
{
m_FailureDescription = (string)tmpObject;
}
}
/// <summary>
/// Methode, die den Body der BizTalk Message in ein Xml Dokument einliest.
/// </summary>
private void ExtractMessageToXmlDocument()
{
// Falls noch nie erfolgreich die Nachricht eingelesen wurde, dann
// jetzt einlesen, ansonsten ist hier nichts zu tun
if (m_MessageBody == null)
{
try
{
Stream bodyStream = m_BizTalkMessage.BodyPart.GetOriginalDataStream();
m_MessageBody = new XmlDocument();
m_MessageBody.Load(bodyStream);
if (bodyStream.CanSeek)
{
bodyStream.Position = 0;
}
}
catch
{
// Fehler sollten ignoriert werden, weil sie abhängig von der
// Struktur der BizTalk Nachricht sind, und weil das Einlesen
// in das Xml Dokument für die Verarbeitung irrelevant ist.
m_MessageBody = null;
}
}
}
/// <summary>
/// Liefert den SOAP Fault Basisknoten als Xml Knoten.
/// </summary>
/// <returns>SOAP Fault Basisknoten als Xml Knoten.</returns>
private XmlNode GetSoapFaultBaseNode()
{
XmlNode retVal = null;
// Nachricht als Xml Dokument einlesen
ExtractMessageToXmlDocument();
// Falls erfolgreich, dann den SOAP Fault Base Knoten finden
if (m_MessageBody != null)
{
retVal = m_MessageBody.SelectSingleNode(c_XPATHSOAPFAULTBASE);
}
return retVal;
}
#endregion
}
}Statische Methode IsSoapFaultMessage:
Die Methode hilft einem zu überprüfen, ob es sich um eine von BizTalk erzeugte SOAP Fault Nachricht handelt. Als Kriterium für den Rückgabewert wird die Kontexteigenschaft
AckType herangezogen, und auf den Wert "
NACK" (Fehler) überprüft. Sollte die Kontexteigenschaft mit dem Wert übereinstimmen, so liefert die Methode
True zurück, andernfalls
False.
Konstruktor mit Parameter IBaseMessage:Der Konstruktor nimmt die BizTalk Nachricht via Interface
IBaseMessage entgegen und überprüft, ob es sich um eine SOAP Fault Nachricht handelt. Sollte dem nicht so sein, so schmeißt der Konstruktor eine Exception.
-> Das Schmeißen von Exception in Konstruktoren ist unter Umständen schlechter Stil. In meinem Anwendungsfall war es aber die einzige Möglichkeit die Verwendung der Klasse eindeutig zu machen.
Wenn es sich um eine SOAP Fault Nchricht handelt, so wird sich die Referenz darauf gemerkt.
Properties:Die Properties für diese Klasse sollten laut Codekommentar recht eindeutig sein.
Verwendung:Bevor man eine Instanz dieses Helfers erzeugt sollte man mittels der statischen Methode
IsSoapFaultMessage überhaupt überprüfen, ob es sich um eine von BizTalk erzeugte SOAP Fault Nachricht handelt. Als Kriterium wird die Kontexteigenschaft
AckType herangezogen, und auf den Wert "
NACK" (Fehler) überprüft.
Beispiel einer Verwendung:
// Überprüfen, ob ein BizTalk SoapFault vorliegt
if (SoapFaultMessage.IsSoapFaultMessage(inMsg))
{
SoapFaultMessage soapMessage = new SoapFaultMessage(inMsg);
throw new Exception(string.Format("BizTalk Fehler: {0} mit Fehlertext: {1}", soapMessage.FailureCode, soapMessage.FailureDescription));
}Fazit:Mit Hilfe dieser Klasse habe ich es geschafft SOAP Fault Nachrichten gekapselt auszuwerten und durch einfachen Zugriff auf die Daten transparent zu halten.
Verbesserungspotenzial:Es gibt bei einer SOAP Fault Nachricht noch weitere Daten, die über Properties zugänglich gemacht werden können. Hier würde ich mit XPathes im Helfer arbeiten.