-
-
Ich habe auf http://dotnet-forum.de einen Artikel mit oben genanntem Thema verfasst. Den kompletten Artikel findet ihr hier: Artikel.
Kurzzusammenfassung:
Einleitung und Zielgruppe:
(Schwierigkeit: mittel)
Das Thema WPF oder WinForms? ist aktuell in jedem Projekt, das .NET Oberfläche bietet eine interessante Frage. Für die Projekte die es sich leisten können beides zu implementieren und anhand der installierten .NET Version dann zu entscheiden, ob WPF verwendet wird oder nicht, für die könnte dieser Artikel von Interesse sein.
Weiterhin zeigt der Artikel wie WPF oder WinForms Anwendungen programmatisch zu starten sind. Die Auswahl, welche Anwendung geladen werden soll wird mittels dem Ansatz von Microkernel und dem dynamischen Laden von Assemblies realisiert. Schaut Euch einfach den Artikel mal an ;-) ...
Euer Timo
-
-
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.
-
-
Mir ist bei einem Fehler aufgefallen, dass XSD.exe zusammen mit dem XmlSerializer so seine Tücken hat.
Ich weiß, dass XSD.exe nicht das gelbe vom Ei ist, weshalb ich hier nicht auf Alternativen eingehe.
Problembeschreibung:
Eine Klasse, die mittels XSD.exe aus einem Schema heraus generiert werden soll. Das Schema sieht z.B. so aus:
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://tempuri.org/TestObject.xsd" elementFormDefault="qualified" targetNamespace="http://tempuri.org/TestObject.xsd" id="TestObject" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="TestObject">
<xs:complexType>
<xs:sequence>
<xs:element name="TestStringField" type="xs:string" />
<xs:element default="-1" minOccurs="0" name="TestIntegerField" type="xs:integer" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Wichtig ist, dass TestIntegerField "minOccurs" und einen "default" besitzt!
Die Klasse, die mittels XSD.exe generiert wird sieht dann so aus:
using System.Xml.Serialization;
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://tempuri.org/TestObject.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://tempuri.org/TestObject.xsd", IsNullable=false)]
public partial class TestObject {
private string testStringFieldField;
private string testIntegerFieldField;
public TestObject() {
this.testIntegerFieldField = "-1";
}
/// <remarks/>
public string TestStringField {
get {
return this.testStringFieldField;
}
set {
this.testStringFieldField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(DataType="integer")]
[System.ComponentModel.DefaultValueAttribute("-1")]
public string TestIntegerField {
get {
return this.testIntegerFieldField;
}
set {
this.testIntegerFieldField = value;
}
}
}
Problem ist, wenn man die Default Werte unangetastet lässt, dass beim Serialisieren mittels dem XmlSerializer diese Xml Knoten nicht mitserialisert werden.
Lösung:
Es gibt hierfür auch bei Microsoft einen Support Beitrag, wo erklärt wird, dass das "By Design" ist.
Es bleibt einem nur übrig, alle Attribute [System.ComponentModel.DefaultValueAttribute("-1")] im generierten Code nachträglich zu entfernen.
Keine glückliche Lösung, aber es ist immerhin eine.
-
-
Hallo liebe Internetwelt,
heute habe ich freundlicherweise einen Internetblog eingerichtet bekommen. Ich bin zunächst zwiegespalten was die Sache hier angeht. Ich möchte es einfach einmal versuchen hier meine Erfahrungen und Erkenntnisse hauptsächlich aus der Kategorie Softwareentwicklung niederzuschreiben.
Wahrscheinlich werde ich es aber auch nicht sein lassen können Beiträge aus der Kategorie "Unsinn" zu verfassen, vor allem wenn es um Comedy oder Fun geht.
Ich werde nun zunächst etwas Zeit benötigen mich mit der Administration zu diesem Blog auseinander zu setzen, mal schauen wann ich hier den ersten sinnvollen Beitrag verfassen kann.
Dann á pros pos Kategorie "Unsinn" schaut euch doch einmal dieses Video an ;-)
Viel Spaß dann in Zukunft mit meinem Blog...
Timo