Nachdem mir vor einiger Zeit auf tutorials.de leider niemand helfen konnte bei dem Versuch ein- und ausgehende Nachrichten bei WCF mitzuloggen und zu untersuchen, dachte ich, dass ich hier einmal meine Erfahrung zur Verfügung stelle:
WCF bietet standardmäßig eine Konfiguration für das MessageLogging. Dieser ist Teil der sog. "Diagnostics"-Einstellungen und vollkommen ausreichend, wenn es wirklich nur um das Loggen an sich geht. Den entsprechenden Artikel dazu findet man im MSDN.
Leider lässt sich dieses Logging nicht so detailiert konfigurieren, wie ich das damals brauchte. Meine Aufgabe war es nämlich die eigentliche Message zu einem gesendeten Objekt zu speichern, um nachhalten zu können, ob der vorgeschaltete Wrapper anständig arbeitete.
Nach einiger Suche, fand ich den Ansatz der "Extensions". Und dieser funktioniert folgendermaßen.
Über die Konfiguration lässt sich einem Endpunkt ein Endpoint-Behavior zuweisen:
<endpoint address="ADDRESS" binding="basicHttpBinding" behaviorConfiguration="AttachClientInspector" contract="CONTRACT" />
Hierzu entsprechend muss natürlich auch ein Behavior namens "AttachClientInspector" vorhanden sein (den Namen könnt ihr euch natürlich selber aussuchen). Dieser sähe ungefähr so aus:
<endpointBehaviors>
<behavior name="AttachClientInspector">
<ClientInspector />
</behavior>
</endpointBehaviors>
Falls ihr das Visual Studio 2005 verwendet, wundert euch nicht, dass der einzelne Tag "ClientInspector"-Tag markiert wird mit dem Text "...invalid child element". Das liegt schlichtweg daran, dass das VS2005 die eigentliche Extension nicht erkennt. Und hier kommen wir auch direkt zum nächsten Schritt, denn der Konfigurationsabschnitt "System.ServiceModel" erlaubt neben "Bindings" und "Behaviors" auch einen Abschnitt namens "Extensions". Hier könnt ihr eine sog. "BehaviorExtension" hinzufügen:
<behaviorExtensions>
<add name="ClientInspector " type="CLASSNAME, ASSEMBLYNAME, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
Der CLASSNAME muss natürlich auf eine bestimmte Klasse zeigen, welche für das Untersuchen der Nachricht geeignet ist. Damit diese Klasse hier akzeptiert wird, muss sie von "System.ServiceModel.Configuration.BehaviorExtensionElement" ableiten UND "System.ServiceModel.Description.IEndpointBehavior" implementieren. Von BehaviorExtensionElement muss man ableiten, damit die Klasse als Extension an das Behavior gebunden werden kann. Sie verlangt die Bereitstellung von BehaviorType, wobei man einfach via typeof die aktuelle Klasse angeben kann und die Methode CreateBehavior, bei der man nur eine Instanz der aktuellen Klasse zurückgeben muss:
public override Type BehaviorType
{
get { return typeof(CLASSNAME); }
}
protected override object CreateBehavior()
{
return new CLASSNAME();
}
IEndpointBehavior hingegen stellt die Methoden zur Verfügung, die schließlich während der Laufzeit aufgerufen werden, sobald die entsprechende Extension greift. Hierbei interessiert uns nur die Methode "ApplyClientBehavior". Diese besitzt einen Parameter "clientRuntime" vom gleichnamigen Typen. Hier lässt sich unser Inspector nun wie folgt einbauen:
clientRuntime.MessageInspectors.Add(new CUSTOMINSPECTOR());
So... damit hätten wir schonmal den Grundstein gelegt, damit der Inspector aufgerufen wird, sobald der entsprechende Endpunkt verwendet wird. Jetzt fehlt nur noch die eigentliche Implementation des Inspectors. Hierzu erstellt ihr eine Klasse mit dem Namen CUSTOMINSPECTOR (die groß geschriebenen Wörter sind natürlich nur Platzhalter, um die Relationen klar zu stellen) und leitet vom Typ "System.ServiceModel.Dispatcher.IClientMessageInspector" ab. Dieses Interface setzt euch zwei Methoden vor: AfterReceiveReply und BeforeSendRequest. Letztere erwartet einen Rückgabetypen vom Typ "object"... schreibt einfach "return null;" hinein, denn der Rückgabewert interessiert uns hier nicht.
Wenn ihr jetzt versucht zu kompilieren, dürfte er nicht meckern und müsste das Projekt anständig bauen. Was natürlich noch immer fehlt, ist die eigentliche Implementation der Inspectors. Hierfür besitzen beide Methoden einen Parameter vom Typ "System.ServiceModel.Channels.Message". Darin enthalten ist die entsprechende Nachricht.
Wenn ihr aber nun das erste Mal versuchen solltet auf die Nachricht zuzugreifen, dürftet ihr nach kurzem genervt sein, denn das Nachrichten-Objekt wird ungültig, sobald ihr zum ersten Mal darauf zugreift. Schön, nicht? ;-)
Aber auch dieses Problem ist recht einfach zu meistern, denn die Message-Objekte besitzen eine Methode "CreateBufferedCopy". Mit folgendem Aufruf könnt ihr euch dann eine "buffered Copy" des aktuellen Message-Objekts erstellen:
MessageBuffer bufferedMessage = request.CreateBufferedCopy(request.ToString().Length);
(Hierbei ist das Message-Objekt "request". Bei "AfterReceiveReply" nennt sich das Objekt hingegen "reply".)
Aus der "Buffered Copy" könnt ihr dann, nachdem ihr eure Analyse abgeschlossen habt, wieder ein MessageObjekt erzeugen:
request = bufferedMessage.CreateMessage();
(Wieder entsprechend "reply" natürlich bei der Reply-Methode. Das Objekt, das hier gefüllt wird, ist das selbe, wie im Parameter übergeben wurde.)
Das ist eigentlich alles, was man dazu wissen muss. Um zu Analysezwecken an den Inhalt zu kommen, hat man dann verschiedene Möglichkeiten:
1.) Aus der "bufferedMessage" kann man via "CreateNavigator" einen XPathNavigator generieren, mit dem man dann die Nachricht untersuchen kann. Außerdem kann man an jedem beliebigen Punkt des XPathNavigators auf die Eigenschaft "InnerXML" zugreifen, welche den Inhalt der untergeordneten Knoten als String bereitstellt.
2.) Man kann, wie vielleicht weiter oben schon bemerkt, aber auch direkt request.ToString() machen und erhält so den Inhalt der Nachricht.
Ich wünsche euch viel Erfolg damit und hoffe ich konnte euch helfen.