Dies ist mein erster Blog-Post und soll den Anfang für eine kleine HowTo-Sammlung bilden, die ich zur Archivierung für mich selbst, aber auch für alle anderen Interessierten hier ablegen möchte.
Die Themen werden sich vor allem, aber nicht ausschließlich, mit der Anwendungsentwicklung mit C# innerhalb von Visual Studio beschäftigen.
Der heutige Post wird ein extrem einfaches aber recht schönes Beispiel zur Erstellung eines selbsthostenden WCF Hosts zeigen. Daneben wird außerdem gezeigt, wie man mit einem kleinen Client auf die Methoden des Hosts zugreifen kann.
Für diejenigen, die sich noch nicht mit WCF beschäftigt haben: WCF steht für "Windows Communication Foundation" und stellt ein leistungsfähiges Framework innerhalb von .NET dar, mit welchem man auf sehr einfache Art und Weise Server/Client-Anwendungen realisieren kann.
Warum hierzu ein weiterer Blog-Post, obwohl im Internet bereits unzählige existieren, die sich mit diesem Thema beschäftigen? Ich wollte für ein kleines Privat-Projekt einen WCF Server/Client erstellen, konnte aber im Internet kein aktuelles, einfaches aber dennoch schönes Beispiel-Projekt finden. Die Quellen derer ich mich während der Recherche bedient habe waren die folgenden:
http://www.codeproject.com/KB/WCF/WCFexample.aspx
http://bloggingabout.net/blogs/dennis/archive/2010/06/16/wcf-simple-example-in-visual-studio-2010.aspx
Die Anforderungen, die ich an mein eigenes Projekt im voraus gestellt habe, konnten die obigen Beispiele leider nicht vollständig abbilden. Wer möchte kann den Teil auch überspringen, hier die Gründe im Detail ;)
Möglichst einfache aber solide Architektur
Im Blog-Eintrag von beBug wird mit zwei einfachen Konsolen-Anwendungen begonnen, was mir sehr gut gefallen hat. Leider wird im Verlauf des Beispiels sehr viel mit Konfigurationstools gearbeitet, die für ein einfaches und vor allem für das Verstehen der Funktionsweise von WCF gedachten Beispiels recht ungeeignet sind. Die Arbeit mit den XML-basierten Konfigurationsdateien wird in meinem Beispiel daher bewusst ausgelassen. Die zweite Quelle, ein Code-Projekt Beispiel, liefert ebenfalls einen vielversprechenden Ansatz, war aber zum Einen etwas aufgeblasen für ein möglichst einfaches Einstiegsprojekt und zum Anderen etwas veraltet, wodurch ich zunächst nicht sicher war, ob mit Visual Studio 2010 größere Änderungen notwendig wären.
Keine Verwendung von svcutil oder Service Reference
In vielen Anleitungen, wie auch in der letzten Quelle, findet man die Verwendung der svcutil.exe (ServiceModel Metadata Utility Tool) oder einer Service-Referenz. Der Zweck hiervon ist es, die vom Host veröffentlichten Methoden im Client nutzen zu können. Der klare Nachteil der beiden Möglichkeiten ist ihre Inflexibilität. Wenn durch den Host eine neue Methode implementiert, oder eine vorhandene geändert wird, so muss man das svcutil erneut ausführen oder die Service-Referenz aktualisieren. Wenn man proprietäre Web-Services wie die von Amazon verwendet ist das völlig ausreichend. Für mein eigenes kleines Projekt wollte ich aber eine Lösung, bei der die Client-Anwendung die Methoden des Hosts in der Entwicklungsumgebung unmittelbar nutzen kann.
Erstellen des Projekts
Nun zum eigentlichen Beispiel: Wir beginnen mit der Erstellung der Solution. Hierfür sollte sichergestellt werden, dass Visual Studio im Administrator-Modus gestartet wurde, ansonsten schlägt das Erstellen des Netzwerk-Sockets im Verlaufe des Beispiels fehl. Sowohl für Host als auch für Client wird hierfür der Einfachheit halber eine Konsolenanwendung verwendet. Wie man die Projekte in einen Windows-Dienst und eine Windows Forms Anwendung portiert versuche ich in einem späteren Post zu zeigen. Zunächst erstellen wir also die Solution, zusammen mit dem WcfHost-Projekt:
Die Interface-Klasse
Nach dem Erzeugen der Solution fügen wir eine neue Class-Datei namens IWcfHost.cs hinzu und fügen dem Projekt eine Referenz zu System.ServiceModel hinzu. System.ServiceModel stellt viele der benötigten Methoden und Schnittstellen für die Erstellung von Server/Client basierten Architekturen bereit.
Zunächst wird aus der Klasse ein Interface gemacht und bekommt zwei einfache Methoden verpasst. Die Implementierung der Methoden wird gleich sauber in einer neuen eigenen Klasse bereitgestellt. Außerdem muss noch die System.ServiceModel Referenz in den usings hinzugefügt werden. Diese wird hier für die Attribute [ServiceContract] und [OperationContract] benötigt. Mithilfe der beiden Attribute wird bekannt gemacht, dass unser Interface und die beiden definierten Methoden Teil des Dienstvertrags zwischen Host und Client sein sollen. Der Dienstvertrag ist notwendig, damit Host und Client wissen, welche Art von Parameter und Rückgabewert durch die Gegenseite erwartet bzw. bereitgestellt werden.
using System.ServiceModel; namespace WcfHost { [ServiceContract] public interface IWcfHost { [OperationContract] string Echo(string pString); [OperationContract] int Sum(int pNum1, int pNum2); } }
Für die Implementierung der Interface-Methoden wird jetzt eine neue Klasse WcfHost erzeugt, welche das zuvor erstellte Interface IWcfHost implementiert. Nachdem wiederum die System.ServiceModel Assembly zu den usings hinzugefügt wurde, kann die Implementierung der zuvor definierten Methoden eingefügt werden. Das Attribut [ServiceBehavior(IncludeExceptionDetailInFaults = true)] ist optional und erleichtert eine detaillierte Fehlerdiagnose durch das Hinzufügen von Fehlerinformationen im Falle eines auftretenden Problems im Host.
using System; using System.ServiceModel; namespace WcfHost { [ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class WcfHost : IWcfHost { public string Echo(string pString) { return String.Format("You've entered: {0}", pString); } public int Sum(int pNum1, int pNum2) { return pNum1 + pNum2; } } }
Start des WCF-Hosts
Nach dem Erstellen des Interfaces und der Implementierung der Methoden kann der WCF-Host nun gestartet werden. Dafür wird in die Program.cs gewechselt und der folgende Code der Main-Methode hinzugefügt.
using System; using System.ServiceModel; namespace WcfHost { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(WcfHost))) { Uri baseAddress = new Uri("net.tcp://localhost:8999/MyService"); NetTcpBinding binding = new NetTcpBinding(); host.AddServiceEndpoint(typeof(IWcfHost), binding, baseAddress); host.Open(); Console.WriteLine("Service started, press any key to finish execution."); Console.ReadKey(); host.Close(); } } } }
Kurz zur Erklärung: Die ServiceHost Variable wird wie der Name schon sagt für das Hosten unseres Diensts verwendet. Dabei ist die Angabe des Typs unserer zuvor erstellten Klasse notwendig. Danach wird die Adresse definiert, unter der unser Host später erreichbar sein soll. Die Kommunikation zwischen Host und Client verläuft TCP basiert, entsprechend beginnt die Uri mit "net.tcp". Das "MyService" am Ende definiert den genauen Endpunkt, auf dem der WCF-Host Anfragen entgegennimmt. Prinzipiell könnte man hier später weitere Endpunkte hinzufügen, für unser Beispiel ist das aber nicht notwendig.
Nach dem Hinzufügen des Endpunkts, kann der ServiceHost "geöffnet" werden. Mit dem Console.Readkey() wird verhindert, dass sich der Host gleich wieder beendet.
Damit ist unser Host fertiggestellt. Er kann jetzt ausgeführt werden, woraufhin eventuell eine Firewall-Anfrage aufpoppt die bestätigt werden sollte, damit Server und Client miteinander kommunizieren können.
Hinzufügen des Client-Projekts
Der Client wird genau wie der Host als Konsolen-Anwendung implementiert. In der Solution wird daher zunächst ein neues Projekt vom Typ Konsolen-Anwendung hinzugefügt und beispielsweise WcfClient genannt. Damit Host und Client gleichzeitig gestartet werden können, sollte in den Eigenschaften der Solution außerdem die Option "Multiple startup projects" ausgewählt und die beiden Projekte auf "Start" eingestellt werden.
Als Projekt-Referenzen wird neben System.ServiceModel nun außerdem unser Host-Projekt "WcfHost" benötigt:
Die Implementierung der main-Methode des Clients ist erstaunlich simpel. Wir benötigen lediglich die Adresse des TCP-Endpunkts und eine ChannelFactory, um einen Kanal zum Host aufbauen und verwenden zu können. Die von der Factory erzeugte Instanz weisen wir einer Klassenvariable vom bereits bekannten Interface IWcfHost zu. Hiermit können jetzt die Methoden aus der Host-Klasse unmittelbar verwendet werden.
using System; using System.Text; using System.ServiceModel; using WcfHost; namespace WcfClient { class Program { private static IWcfHost host = null; static void Main(string[] args) { Uri baseAddress = new Uri("net.tcp://localhost:8999/MyService"); EndpointAddress address = new EndpointAddress(baseAddress); NetTcpBinding binding = new NetTcpBinding(); ChannelFactory<IWcfHost> factory = new ChannelFactory<IWcfHost>(binding, address); host = factory.CreateChannel(); Console.WriteLine("Please enter some words."); string input = Console.ReadLine(); // read input string output = host.Echo(input); // send to host, receive output Console.WriteLine(output); // write output int sum = host.Sum(5, 8); Console.WriteLine(sum); Console.ReadKey(); } } }
Ergebnis
Somit ist unser Projekt fertiggestellt und kann gestartet werden. Nach dem Buildvorgang sollten beide Applikationen selbstständig starten. Der Host wartet nach dem Start auf Anfragen während der Client von uns eine Tastatureingabe erwartet die er an den Host weiterleiten möchte:
Nach der Eingabe von ein paar Wörtern erhalten wir vom Host eine Rückgabe zusammen mit dem Ergebnis aus der Beispiel-Addition aus unserer Main-Methode.
Das vollständige Projekt kann über diesen Link heruntergeladen werden:
https://skydrive.live.com/?cid=d5f85d5594cec9d9&id=D5F85D5594CEC9D9%21133#
Das war's dann für heute, ich hoffe der Post konnte dem ein oder anderem weiterhelfen!
Liebe Grüße
Daniel