WCF-Dienst mit Rückkanal

WCF ist als Plattform für Services eine echt feine Sache. Doch nicht alles ist so einfach, wie es manche Internet-Ressourcen suggerieren. Die Duplex-Technik per wsDualHttpBinding ist so ein Beispiel.

Einleitung

Das gewünschte Szenario ist relativ einfach beschrieben. Man hat einen Server und einen Client, die beide per HTTP miteinander kommunizieren sollen. Der Schwerpunkt liegt nun auf dem „miteinander“. Normalerweise ruft man eine Methode auf dem Server auf, und der liefert etwas als Rückgabewert zurück. Diese recht eingeschränkte Kommunikation kann man technisch noch erweitern, indem man dem Server die Adresse von Rückrufmethode mitgibt (Delegates), die er dann genau wie Ereignisse aufrufen kann.

Wer jetzt fragt, wozu man das wohl braucht, der sei auf Szenarien verwiesen, in denen der Server nach dem Aufruf einer Methode etwas tun soll, dass sehr lange dauern könnte und später eine Information sendet, wie in Abb. 1 dargestellt.

Grobe Darstellung des gedachten Workflows

Hier reicht es nun nicht mehr aus, einfach nur auf die Rückgabe zu warten, weil der Client dann solange blockiert wäre, wie der Server zur Arbeit benötigt.

Umsetzung in WPF

Der Server

In WPF wird so ein Konstrukt dadurch möglich gemacht, dass zunächst einmal das Binding anstelle von wsHttp* oder basicHttp* auf wsDualHttpBinding gesetzt wird. Ein Service, der dies unterstützt könnte wie folgt definiert sein:


<service name="MyNamespace.MailService">
 <endpoint address="" binding="wsDualHttpBinding" bindingConfiguration=""
contract="MyNameSpace.IMailService">
 <identity>
 <dns value="localhost" />
 </identity>
 </endpoint>
 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>

Beachtenswert ist vor allem die Definition des Bindings in Zeile 2 im endpoint-Tag.

Das allein bewirkt aber nichts, außer dass man beim Testen des Services eine Fehlermeldung bekommt, die grob gesagt andeutet, dass nichts innerhalb des Services dem Dual-Binding entspricht.

Der nächste Schritt ist, ähnlich wie bei den Contracts der Services, einen Vertrag für die später zu nutzenden Rückrufmethoden fest zu legen. Dazu legt man einfach ein neues Interface an und definiert hierin Methoden als OperationContract mit dem Zusatzattribut OneWay:

[ServiceContract]
public interface ICallbacks
{

    /// <summary>
    /// Is called by the server if a mail was sent.
    /// </summary>
    /// <param name="strReceiver">The server bypasses the receipient here.</param>
    [OperationContract(IsOneWay = true)]
    void MailSuccessCallback(string strReceiver);

    /// <summary>
    /// Is called by the server if a mail could not be send.
    /// </summary>
    /// <param name="strReceiver">The server bypasses the receipient here.</param>
    /// <param name="strMessage">The error message.</param>
    [OperationContract(IsOneWay = true)]
    void MailErrorCallback(string strReceiver, string strMessage);
}

Ich habe mal ein Beispiel gewählt, dass wirklich etwas tut. Der Vollständigkeit halber sei erwähnt, dass dieses Interface im WCF-Service-Projekt eingebunden wird! Ein Detail, das viele Tutorials gern vergessen. Genutzt wird dieses Konstrukt im Server nun folgendermaßen:


ICallbacks cbaThis = OperationContext.Current.GetCallbackChannel<ICallbacks>();

cbaThis.MailErrorCallback(string.Empty, ex.Message);

Der eigentliche Service-Contract auf dem Server muss nun ebenfalls so angepasst werden, dass er deutlich macht, dass er rückkanalfähig ist:

[ServiceContract(Namespace = "http://www.eniware.info/ibm/RESOBaseEniware", 
                    CallbackContract = typeof(ICallbacks), 
                    SessionMode = SessionMode.Required)]
public interface IMailService
{

    /// <summary>
    /// Sends the content of a byte-array as e-mail-attachment to some receipients.
    /// </summary>
    /// <param name="astrReceipients">An array of smtp-addresses.</param>
    /// <param name="strFilename">A filename bypassed by the IFileService before.</param>
    /// <returns>0, if sending succeeded, otherwise an error-code.</returns>
    [OperationContract]
    int SendMail(string[] astrReceipients, string strFilename);

}

Entscheidend hier ist vor allem das Argument CallbackContract im ServiceContract-Attribut, das angibt, welches Interface den Rückruf-Vertrag definiert.

Dieses Beispiel zeigt grob die Verwendung. Der Trick ist der, dass nun der sog. OperationContext genutzt wird, um einen CallbackChannel zu öffnen. Dieser wird – wie immer in WCF – über den Typ des Objektes, das mit diesem Channel behandelt werden soll (ICallbacks) initialisiert.

Auch das klappt jetzt aber nicht einfach so, sondern erfordert ein wenig Code im Client, der den WCF-Service nutzt.

Der Client

An irgendeiner Stelle wird ein Dienstverweis zu einem Client (z.B. einer Konsolenanwendung) hinzugefügt. Das Visual Studio erkennt dabei die Bindungen des Servers und trägt also auch für unseren MailService. Hier brauchen wir also i.d.R. nichts zu tun. Irgendwann wird dann ein Client-Objekt für den Service instantiiert und über dieses die eigentliche Dienstmethode gerufen. Damit diese eine Rücksprung-Adresse hat, benötigen wir erstmal eine Klasse, die das Interface ICallbacks im Client implementiert:

public class MyClientCallback : MailService.IMailServiceCallback
{

    public void MailSuccessCallback(string strReceiver)
    {
        Console.WriteLine("Mail erfolgreich an {0} gesendet", strReceiver);
    }

    public void MailErrorCallback(string strReceiver, string strMessage)
    {
        Console.WriteLine("Server-Fehler: " + strMessage);
    }
}

Wie man sieht, ist das für einen C#-Entwickler nichts erhebendes. Wichtig ist nur, dass man erst den Dienstverweis hinzufügt und dann das Interface implementiert, da das Projekt es ansonsten ja noch nicht kennen kann.

Nachdem das erledigt ist, muss als letztes noch der bereits erwähnte Kontext des Services mit dem Objekt MyClientCallback bekannt gemacht werden. Hintergrund ist der, dass der später eröffnete Kanal wissen muss, welches Objekt seine Rückrufmethoden denn nun auf dem Client bereit hält:


InstanceContext ctx = new InstanceContext(new MyClientCallback());
MailService.MailServiceClient cliMail = new MailService.MailServiceClient(ctx);
cliMail.SendMail("fox.moulder@fbi.gov", "test.txt");

Man übergibt dem Service einfach einen neuen InstanceContext mit einer Instanz des MyClientCallback.

Testen

Führt man dies nun in seiner lokalen Testmaschine aus, funktioniert alles wunderbar. Der Service versieht seinen Dienst und der Client spuckt die Meldungen des Servers aus, als hätte der tatsächlich ein Event erzeugt.

Verteilt man den fertigen Service nun aber auf eine andere Maschine und startet dann das Projekt mit den Dienstverweisen dorthin, wird ein Fehler ähnlich folgendem erscheinen:

Fehler nach Verteilung des Dienstes
Fehlemeldung nach Dienstverteilung

Das Problem liegt darin, dass wir durch das wsDualHttpBinding nichts anderes definieren, als das der Client aus Sicht des Servers ebenfalls ein WCF-Service wird. Nicht umsonst ist die Definition des Duplex-Kanals so ähnlich zu der eines normalen WCF-Kanals. In dem Moment, in dem wir dem Dienst seinen Kontext mit unserem Rückrufobjekt übergeben, wird de facto ein Service auf dem Client gestartet. Dieser orientiert sich technisch am eingerichtet http-Binding und ist selbst auch HTTP-basiert. Das Problem ist nun, dass der Server nicht weiß, wo der Client eigentlich ist. Normalerweise umgeht man das Problem nun, indem man die Server-Konfiguration anpasst:

<wsDualHttpBinding>
  <binding name="DualHttp" clientBaseAddress="http://client" />
</wsDualHttpBinding>

Man teilt dem Server also mit, unter welcher Adresse er den Client finden kann. Genau da liegt aber das Problem, denn normalerweise hat ein typischer Client keine feste Adresse. Das Einsatzgebiet von wsDualHttp ist also eigentlich eher die Kommunikation zwischen Diensten, deren Adressen ja von vornherein fest stehen bzw. sauber konfiguriert werden können.

Mir war es in diesem Artikel nur wichtig, dem Leser auf einfache Art und Weise Zugang zu der Idee von wsDualHttp zu geben und aufzuzeigen, dass sein Einsatz durchaus Grenzen hat.

2 Antworten auf „WCF-Dienst mit Rückkanal“

  1. sorry für die Rechtschreibung

    *seufz

    Da les ich mich in Vorfreude durch und dawei , wieder nicht die Lösung .

    Ich will doch nur meine Clients untereinander kommunizieren lassen.
    Beide sind hinter verschiedenen Netzen von Internet Seite durch Firewall geschützt also geht nur die Kommunikation über einen Web Service im Internet .
    Dieser Web Service soll aber nicht die Funktionen beinhalten sondern nur per Rückkanal zu einem anderen Service die funktionsaufrufe und datenrückgaben weiterleiten(kein wcfrouting).

    wie stell ich das an ohne die Firewall anzufassen und nur per http oder https Protokoll?

    nochmal zur Vereinfachung
    + 1 Web Service + SQL Datenbank hinter der Firewall im Firmennetz

    + 1 Webservice im Rechenzentrum(Internet)

    + mehrere Clients in anderen Firmennetzen hinter der Firewall

    der Web Service im Firmennetz soll Verbindung zum Web Service im Rechenzentrum(Internet) aufbauen und halten mit Rückkanal(ala Teamviewer).

    Die Clients verbinden sich mit dem Rechenzentrum(Internet) Web Service und bekommen die Daten auch von diesem zurück.

    Der Rechenzentrum Service holt sich die Daten aber vom Firmennetz Rückkanal Web Service

    Ist das zu realisieren ???

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.