Die Micosoft Message Queue ist eine feine Sache zur asynchronen Verarbeitung von Daten. Die .NET-Implementierung im
Namensraum System.Messaging kennt dabei zwei hauptsächtliche Objekte: MessageQueue und
Message. Ersteres muss man öffnen, um letzteres absetzen zu können.
Die Nachrichten, die eine Message beinhalten kann, werden über sog. Formatter in ein für die Gegenstelle
lesbares Format gebracht. In .NET ist der Standard-Formatter der XmlMessageFormatter, der beliebige, serialisierbare
Objekte in XML und dann in ein Stream-Objekt umsetzt, dass an die Message-Queue verschickt wird.
Eine Beispiel-Implementierung mit zwei Message-Queues (eine zum schreiben und eine zum lesen) könnte folgendermaßen aussehen:
'*** Neue In-Queue
Dim mqIn As New MessageQueue(".\private$\my_in_queue")
'(Festlegen, dass eine ID benötigt wird)
mqIn.MessageReadPropertyFilter.CorrelationId = True
'*** Neue Out-Queue
Dim mqOut As New MessageQueue(".\private$\my_out_queue")
'*** Nachricht versenden
Dim msgIn As New Message(MyObject)
'(Antwort-Queue festlegen)
msgIn.ResponseQueue = mqOut
'(Formatter festlegen und Typ bekannt machen)
msgIn.Formatter = New XmlMessageFormatter(New Type() {GetType(MyObject)})
'(Nachricht senden)
mqIn.Send(msgIn)
'*** Antwort empfangen
Dim msgOut As New Message
Try
'(max. 30 Sekunden nach der Antwort auf die Nachricht suchen)
msgOut = mqOut.PeekByCorrelationId( _
msgIn.Id, _
New TimeSpan(0, 0, 30))
Catch ex As Exception
'... Fehler "Server antwortet nicht" verarbeiten
End Try
Dim obj As MyObject
Try
'(Nachricht abrufen)
msgOut = mqOut.ReceiveById(msgOut.Id)
'(Formatter festlegen und Typ bekannt machen)
msgOut.Formatter = New XmlMessageFormatter(New Type() {GetType(MyObject)})
'(Objekt abrufen)
obj = msgOut.Body
Catch ex As Exception
'... Fehler verarbeiten
End Try
Das sieht nett aus, oder? Man nehme ein x-beliebiges, serialisierbares Objekt wie MyObject und werfe es der Queue
zum Frass vor. Der Formatter kümmert sich selbständig um die Konvertierung und wir haben nichts weiter zu tun.
Das Problem
Meine Aufgabenstellung kürzlich war allerdings etwas anders. Es ging um kleine XML-Schnipsel, die zwischen einem Navision-Server
und meinem .NET-Code ausgetauscht werden sollten. Für jede einzelne Anweisung extra Objekte zu erzeugen,
schien mir etwas zu aufwendig, also entschied ich mich, den XML-Code über Linq to XML zu erzeugen und direkt in die
Queue einzustellen.
Dim xMsg As XElement =
<data>
<property_1>Test</property_1>
<property_2>Noch ein Test</property_2>
</data>
...
Dim msgIn As New Message(xMsg.ToString())
...
Was nun passierte war, das der XMLFormatter beim Senden der Nachricht anlief und mir brav meinen XML-Code in XML packte, d.h.
er ersetzte die spitzen Klammern durch HTML-Entities und umschloss den Text mit <string></string>.
Irgendwie hatte er ja recht, denn String ist ein Objekt und so verpackt man Text vernünftig in XML.
Mit Erstauen stellte ich dann fest, dass Microsoft nicht daran gedacht hatte, dass man bei der Verwendung der Message-Queue
vielleicht bereits über das nötige XML verfügt! So sinnvoll die drei veschiedenen Formatter (XmlMessageFormatter,
BinaryMessageFormatter und ActiveXMessageFormatter) auch sind, einfach XML einstellen is' nicht...
Da alle Formatter schlußendlich nicht anderes machen als den Wert der Eigenschaft Message.Body in einen
Stream umzuwandeln (in die Eigenschaft Message.BodyStream), habe ich mir für die Typen XDocument und
XElement einen eigenen Formatter geschrieben, der von IMessageFormatter ableitet und den Inhalt direkt in
einen Stream umwandelt.
Die wichtigsten Methoden sind hierbei Write und Read:
Public Sub Write(ByVal message As Message, ByVal obj As Object) _
Implements IMessageFormatter.Write
If message Is Nothing Then
Throw New System.ArgumentNullException("message")
End If
Dim xd As New XDocument
If TypeOf message.Body Is XDocument Then
xd = message.Body
ElseIf TypeOf message.Body Is XElement Then
xd = New XDocument(message.Body)
End If
Dim ms As New MemoryStream
Dim sw As New StreamWriter(ms)
xd.Save(sw)
sw.Flush()
message.BodyStream = ms
message.BodyType = 0
End Sub
Public Function Read(ByVal message As Message) As Object _
Implements IMessageFormatter.Read
If message Is Nothing Then
Throw New System.ArgumentNullException("message")
End If
Dim sr As New StreamReader(message.BodyStream)
Dim xr As XmlReader = XmlReader.Create(sr)
Dim xd As XDocument = XDocument.Load(xr)
Return xd
End Function
Beim Schreiben einer Nachricht wird das XDocument in einen StreamWriter gespeichert und direkt in den
BodyStream geschrieben und beim Lesen wird Letzterer über einen StreamReader in einen XMLReader
umgewandelt, mit dem das auszugebende XDocument erzeugt wird.
Obiges Beispiel muss für den Einsatz von XLinq nur minimal angepasst werden:
Dim xMsg As XElement = <data>
<property_1>Test</property_1>
<property_2>Noch ein Test</property_2>
</data>
...
Dim msgIn As New Message(xMsg)
msgIn.Formatter = New XLinqMessageFormatter
...
msgOut = mqOut.ReceiveById(msgOut.Id)
msgOut.Formatter = New XLinqMessageFormatter
obj = msgOut.Body
...
Happy queueing ;)
Downloads
XLinqMessageFormatter.vb.txt