14. Okt 2019
Lesedauer 15 Min.
WCF ist tot! Und was jetzt?
Optionen für einen Umstieg von WCF, Teil 1
.NET 5 soll ab 2020 alles vereinen: eine Runtime und ein Framework für alle Plattformen. Nicht weitergeführt wird darin allerdings WCF. So sehen die Alternativen aus.

Am 6. Mai 2019 hat Microsoft bekannt gegeben, wie es mit dem .NET Framework und .NET Core weitergehen soll [1][2]. Und das hört sich erst mal nicht schlecht an: Die Zukunft von .NET ist .NET Core und die Zukunft von .NET Core heißt .NET 5. Das bedeutet im Klartext, dass .NET Framework 4.8 die letzte Version der Anfang 2002 erstmals vorgestellten Plattform ist. Und es bedeutet, dass .NET 5 der Nachfolger von .NET Core 3.0 wird. Geplant ist das erste Release von .NET 5 für November 2020, danach soll es jedes Jahr ein Release geben. Die jeweils geraden Versionsnummern werden dabei Long Term Support (LTS) bieten.So weit, so gut. Das seit der Einführung von .NET Core 1.0 im November 2014 gespaltene .NET-Ökosystem wird nun also wieder zu einer einheitlichen Version zusammengeführt. Dabei soll laut Microsoft all das Gute von .NET Core beibehalten werden. Gleichzeitig soll durch die Vereinheitlichung alles einfacher werden und .NET 5 soll eine breitere Zielgruppe abdecken. Doch wo Licht ist, ist bekanntermaßen auch Schatten. Bereits in .NET Core 3.0 werden keine Features aus dem .NET Framework mehr übernommen. Und mit dem Ende des .NET Frameworks als solchem werden auch Teile, die es bis dato nicht nach .NET Core geschafft haben, nicht mehr portiert und weiterentwickelt.Auf der Liste der Opfer [3] stehen Web Forms, die Windows Communication Foundation (Server) und die Windows Workflow Foundation. Die Nichtportierung wird mit dem großen Aufwand und der mangelnden Kompatibilität zu anderen Plattformen begründet. Man will die Entwickler jedoch nicht gänzlich im Regen stehen lassen und bietet deshalb Migrationspfade zu anderen Technologien an. Blazor soll die Nachfolge von Web Forms antreten, für die Workflow Foundation gibt es einen Open-Source-Port für .NET Core. Und schließlich soll gRPC in die Fußstapfen von WCF treten.
Warum hatten wir noch mal WCF benutzt?
Im Folgenden geht es ausschließlich um die WCF und ihre möglichen Nachfolger. Es ist zu begrüßen, dass Microsoft bereits eine technologische Nachfolge für WCF vorschlägt. Ob gRPC für alle, die jetzt noch die WCF im Einsatz haben, einen guten Ersatz darstellt, ist fraglich und lässt sich wohl auch nur im Einzelfall klären. Zum Glück gibt es aber auch noch eine Reihe anderer Optionen, auf die man ausweichen könnte, die von Microsoft jedoch nicht so offensiv beworben werden.Die passende Technologie zur Ablösung der WCF zu finden erfordert die Frage, warum man eigentlich zuvor auf dieses Pferd gesetzt hat – nötig ist also ein Blick auf die Einsatzszenarien der WCF. Als Kind einer Zeit, als die Präsenz des Internets und der allgemeine Vernetzungsgrad noch nicht so umfassend waren wie heute, kam die WCF als Backend für Desktop-Applikationen auf Basis von WinForms oder der WPF zum Einsatz. Geradezu gesetzt war sie auch beim Einsatz von Silverlight. Auch für die Kommunikation zwischen verschiedenen Diensten und Servern kam sie zum Einsatz, Stichwort Service Oriented Architecture (SOA). Die häufigsten Einsatzgebiete lagen also im Fat-Client- beziehungsweise Backend-Bereich. Denn obwohl sie auch HTTP als Transportschicht nutzen konnte, wie zum Beispiel bei Silverlight, zeigte die WCF ihre Stärken dort, wo nicht per HTTP kommuniziert werden sollte. Hier stand eine große Bandbreite an Optionen zur Verfügung, etwa TCP, Named Pipes oder MSMQ.Warum die WCF an Popularität verloren hat, erklärt sich, wenn man den Blick auf ihre schwächeren Seiten richtet. So ist ein Aufruf von WCF-Diensten aufgrund der Natur des SOAP-Protokolls mit reinen HTTP-Bordmitteln nicht so einfach. Für die WCF ist HTTP nichts weiter als eine Transportschicht, daher lässt sie die meisten Gegebenheiten des Protokolls, wie URIs oder HTTP-Verben, außer Acht. Die starke Verbreitung von HTML5- und JavaScript-basierten SPAs und mobilen Apps, die auch in diejenigen Domänen vorgedrungen sind, in denen ansonsten die Desktop-Applikationen herrschten, hat diese Schwächen sehr deutlich hervortreten lassen. Es war eine Lösung gefragt, die über natives HTTP leicht aufzurufen ist, was über eine tiefere Nutzung der Facetten des Protokolls zu erreichen war. Für Microsoft steigt an dieser Stelle ASP.NET Web API in den Ring, das seit seinem Erscheinen als Alternative zur WCF gehandelt wurde. Die Idee hinter Web API, nämlich eine möglichst hohe Erreichung der REST-Prinzipien [4] durch Ausnutzung aller Facetten von HTTP, begründet aber eine ganz andere Denkweise als der RPC-Ansatz der WCF. Außerdem funktioniert es nur per HTTP und nicht mit anderen Transporten.Es gab und gibt also gute Gründe, nicht auf Web API zu setzen, und in diesen Anwendungsfeldern stellt auch gRPC nicht unbedingt eine gute Alternative dar.Wie ist WCF?
Um eine passende Alternative zu finden, muss man also die Eigenschaften identifizieren, die im eigenen Anwendungsfall verwendet werden, und nach einer Technologie suchen, die diese Eigenschaften ebenfalls mitbringt. Um einen solchen Vergleich zu ermöglichen, sollen die Eigenschaften der WCF aufgenommen und zu Vergleichskriterien verallgemeinert und systematisiert werden. Anhand dieser Kriterien wird ein Vergleich verschiedener Technologien möglich.Zentrales Element der WCF ist der Endpunkt, der sich aus dem ABC von Address, Binding und Contract zusammensetzt. Die Adresse stellt den Transport-Endpunkt dar, unter dem der Dienst erreichbar ist. Diese hängt natürlich vom unterliegenden Transport ab. Wird HTTP verwendet, so ist die Adresse ein URL. Im Fall von TCP beginnt die Adresse nicht mit dem Präfix http, sondern mit net.tcp. Das Transportprotokoll in Verbindung mit dem Maschinennamen und optional dem Port definiert die Basisadresse des Dienstes, an die optional noch ein Pfad angehängt werden kann.Das Binding ist eine Sammlung von Parametern, die bestimmen, wie ein Client mit dem Service zu kommunizieren hat. Das umfasst natürlich das Transportprotokoll, das sich schon in der Adresse widerspiegelt, es spielen aber noch viele weitere Aspekte eine Rolle: Wie sollen die Nachrichten codiert werden, wie werden die Nachrichten oder die Verbindung gesichert, ist die Reihenfolge von Nachrichten von Bedeutung oder müssen diese gar transaktional verarbeitet werden? Alle diese Fragen müssen in konsistenter Weise beantwortet werden, und Dienst und Client müssen sich an die gleichen Richtlinien halten, damit eine Kommunikation überhaupt möglich ist.Der beziehungsweise die Contracts definieren, was ein Dienst leisten kann. In der WCF unterscheidet man verschiedene Arten von Kontrakten. Der Service Contract gruppiert eine Menge von Operationen, die ein Dienst unterstützt, eine einzelne Operation wird durch einen Operation Contract beschrieben. Die Datentypen, die der Dienst akzeptiert oder liefert, werden in Form von Data Contracts beschrieben. Alle Kontrakte können innerhalb der WCF plattform- und technologieneutral dargestellt werden.Der gleiche Dienst kann in der WCF unter mehreren Endpoints verfügbar gemacht werden. Dabei können alle drei Parameter variiert werden. So kann ein Dienst mit dem gleichen Contract unter verschiedenen Adressen mit unterschiedlichen Bindings angeboten werden, zum Beispiel per HTTP und per TCP. Oder ein Dienst stellt über verschiedene Kontrakte unterschiedliche funktionale Teilmengen an mehreren HTTP-Endpunkten zur Verfügung, um verschiedene Nutzergruppen (Admins, User) zu adressieren. Bei der Wahl der Parameter müssen Adresse und Binding zueinander konsistent sein. Ein Dienst mit einer TCP-Adresse kann nicht mit einem HTTP-Binding bereitgestellt werden.Ein WCF-Dienst kann Metadaten über einen Endpunkt technologieneutral in Form von WSDL-Dokumenten (Web Service Description Language) [5] bereitstellen. Dabei handelt es sich um ein XML-Dokument, in dem in standardisierter Form Informationen über das Binding und die Contracts dargestellt sind. Anhand dieser Beschreibung ist es möglich, automatisch Proxy-Klassen zum Aufruf des Dienstes in verschiedenen Sprachen generieren zu lassen.Die Clients rufen den Dienst über SOAP auf, das Simple Object Access Protocol [6]. Dabei handelt es sich um ein ebenfalls standardisiertes Protokoll zum Aufruf entfernter Dienste. Sofern ein Dienst per HTTP-Binding zu erreichen ist, kann man diesen aus einer Webapplikation heraus aufrufen. Die meisten WCF-Clients werden aber in einer Backend-Sprache wie zum Beispiel C# implementiert sein.Von Äpfeln und Birnen – systematische Vergleichskriterien
Um WCF sinnvoll mit anderen Technologien zu vergleichen, müssen die oben genannten Merkmale in systematische Vergleichskriterien synthetisiert werden. Grundlegend lassen sich die Eigenschaften eines WCF-Systems, das ja sowohl den Server als auch die Clients umfasst, in die Kategorien serverseitige Architektur und clientseitige Architektur aufteilen. Eine dritte Kategorie bildet die Verbindung zwischen Client und Server, die wir als Bindungen bezeichnen werden.Jede dieser Kategorien enthält Eigenschaften, die pro Technologie unterschiedliche Ausprägungen besitzen können und die im Folgenden aus der WCF abgeleitet werden. In Tabelle 1 sind die Kategorien sowie ihre Eigenschaften mit den möglichen Ausprägungen und einer kurzen Beschreibung und der Ausprägung für die WCF zusammengefasst.Tabelle 1: Vergleichskriterien für Service-Technologien
|
Für die Server- wie auch für die Clientseite stellt sich generell die Frage, welche Sprachen und Plattformen unterstützt werden. Da es sich bei der WCF um ein Produkt von Microsoft handelt, verwundert es nicht, dass sie sich in C# beziehungsweise in jeder .NET-Sprache implementieren lässt. Die erforderliche Plattform ist das .NET Framework ab Version 3.0. Eine weitere allgemeine Eigenschaft ist der Architekturstil, der sich in einer Ausprägung des Richardson Maturity Model (RMM) [4] ausdrücken lässt. Die WCF arbeitet mit Remote Procedure Calls (RPC), dies entspricht dem RMM-Level 0.Die serverseitige Architektur der WCF wird durch den Endpunkt mit seinen ABC-Eigenschaften definiert. Da die Aspekte, die das Binding betreffen, in eine eigene Kategorie ausgelagert wurden, bleiben hier noch die Adressierung und der Kontrakt zu betrachten. Die Adressierung in der WCF läuft über einen Endpunkt für den gesamten Dienst. Über diesen Endpunkt werden alle Operationen aufgerufen. Historisch gesehen ist dies oft eine SVC-Datei. Sofern ein HTTP-Transport verwendet wird, wird stets nur das POST-Verb verwendet, während die HTTP-Statuscodes nicht verwendet werden. Da HTTP für einige Technologien ein reiner Transport, für andere dagegen Grundlage der gesamten Architektur ist, wird die Adressierung im Speziellen auf ihre Ausnutzung der HTTP-Verben bewertet.Die Kontrakte stellen die Service-Definition dar. Zu unterscheiden sind der Service Contract, der Operation Contract und der Data Contract. Im Fall der WCF ist der Service Contract ein mit Attributen annotiertes C#-Interface, dessen ebenfalls attributierte Methodendefinitionen den Operation Contract darstellen. Die Parameter und Rückgabewerte bilden die Data Contracts, die als attributierte POCOs umgesetzt werden. Eng verknüpft mit der Definition des Service ist die Art und Weise der Service-Implementierung. Im Fall der WCF handelt es sich hier um eine C#-Klasse, die das Interface implementiert, welches den Service Contract definiert.Jeder WCF-Dienst stellt eine Beschreibung seiner selbst in Form von WSDL-Dokumenten bereit. Dies betrifft den Aspekt der Metadaten, also die Fähigkeit einer Service-Technologie, eine Beschreibung eines Dienstes zu liefern. Diese ist meist unabhängig von der eigentlichen Technologie, in welcher der Dienst implementiert ist.In der Kategorie Bindungen werden Eigenschaften zusammengefasst, die für eine erfolgreiche Kommunikation zwischen Server und Client erforderlich sind. Allen voran sind hier natürlich die Transporte zu nennen, die eine Technologie unterstützt. Hier liegt eine der Stärken der WCF, da hier eine große Bandbreite von HTTP(S) über TCP bis hin zu MSMQ angeboten wird. Damit verbunden ist das Protokoll, über das Client und Server kommunizieren. Im Fall der WCF ist dies SOAP, nicht etwa HTTP, das hier nur als Transportschicht zum Tragen kommt. Dienste, die auf den REST-Prinzipien beruhen, verwenden HTTP sowohl als Transport als auch als Protokoll. Das Protokoll legt die Regeln für den Datenaustausch fest, die Serialisierung gibt die Sprache an, die dabei gesprochen wird. Die in der WCF versendeten SOAP-Nachrichten werden in XML serialisiert, können aber auch binär übertragen werden. Schließlich unterscheiden sich Service-Technologien noch darin, welche Kommunikationsmuster unterstützt werden. Auch hier bietet die WCF eine große Bandbreite von One Way über Request/Response, Publish/Subscribe und Streaming.In der Kategorie der clientseitigen Architektur steht, wie auch in der serverseitigen Kategorie, die Eigenschaft Sprache an erster Stelle. Hier gilt es ein besonderes Augenmerk darauf zu richten, ob eine Technologie plattformübergreifend angesprochen werden kann, also ob ein Client in einer anderen Sprache geschrieben sein kann als der Dienst. Aufgrund der hohen Relevanz von Webtechnologien sollte außerdem beleuchtet werden, ob ein Dienst auch von JavaScript/TypeScript angesprochen werden kann. Einen WCF-Dienst kann man über viele Sprachen ansprechen (zum Beispiel PHP, Java), ausgerechnet im Bereich JavaScript sieht es allerdings nicht so gut aus, da es nur wenige Bibliotheken gibt, die einem die Erstellung der SOAP-Nachrichten abnehmen. Das Einsatzgebiet des Clients gibt an, wo die Clients für gewöhnlich ihren Dienst tun. Da sich die WCF mit dem Web etwas schwertut, sind hier vor allem Fat Clients und das Backend zu nennen. Bleibt hier nur noch die Frage, wie der Aufruf des Dienstes realisiert ist. Im Fall der WCF kann dies über einen generischen Proxy, eine sogenannte Channel Factory, erfolgen. Alternativ kann man sich aus dem WSDL auch einen Client generieren lassen.
Ein Vergleichsmuster, bitte!
Anhand der Eigenschaften aus Tabelle 1 wird ein Vergleich verschiedener Service-Technologien möglich. Fehlt nur noch ein WCF-Beispieldienst als Vergleichsmuster. Vorher sei noch der Hinweis erlaubt, dass ein solcher Vergleich ein weites Feld ist, nicht alle Eigenschaften sind absolut trennscharf. Viele Technologien sind so gut erweiterbar, dass man ihnen fast jedes Nutzungsmuster aufzwingen kann. Die Vergleichskriterien verstehen sich als Raster zur Einordnung von Technologien – nicht als deren technische Dokumentation. Jede Technologie wird dabei aus der Sicht des Mainstreams betrachtet. Ob man der WCF auch beibringen kann, JSON zur Serialisierung zu verwenden, sei demnach dahingestellt, weil es nicht dem normalen Nutzungsverhalten entspricht.Um nun nicht völlig auf dem Trockenen zu vergleichen, soll in jeder Service-Technologie ein einfacher Beispieldienst implementiert werden, dessen Service Contract in Listing 1 zu sehen ist. Der Dienst bietet eine Operation namens Execute an, die eine Berechnung bestehend aus zwei Operanden und einem Operator übernimmt und als Ergebnis das Resultat dieser Berechnung liefert. Es handelt sich hierbei um eine klassische Request-Response-Kommunikation. Um das Ganze etwas spannender zu machen, bietet der Dienst außerdem die Möglichkeit, sich für eine Reihe von Zufallszahlen zu registrieren (GenerateRandomNumbers), die mit einem variablen zeitlichen Abstand generiert werden. Diese Methode liefert nicht direkt ein Ergebnis, sondern liefert die Zufallszahlen über einen Callback-Kontrakt IRandomNumber. Dieser stellt einen Rückkanal vom Server zum Client dar, über den der Server eine Kommunikation initiieren kann. Dabei handelt es sich also um ein Publish-Subscribe-Kommunikationsmuster. In der WCF gilt die Limitierung, dass ein Callback-Kontrakt nur One-Way-Operationen enthalten darf, also solche Methoden, die void liefern. Eine einfache Implementierung dieses Dienstes ist in Listing 2 dargestellt. Den kompletten Quellcode finden Sie auf GitHub [7].Listing 1: Kontrakte des Beispieldienstes
[ServiceContract(CallbackContract = <br/> <span class="hljs-keyword">typeof</span>(IRandomNumber))] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ICalculatorService</span> <br/> { <br/><br/> [OperationContract] <br/> <span class="hljs-function">CalculationResult <span class="hljs-title">Execute</span>(<span class="hljs-params">Calculation </span></span><br/><span class="hljs-function"><span class="hljs-params"> calculation</span>)</span>; <br/><br/> [OperationContract] <br/> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">GenerateRandomNumbers</span>(<span class="hljs-params">Guid requestId, </span></span><br/><span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">int</span> count, <span class="hljs-keyword">int</span> delayInMs</span>)</span>; <br/><br/> } <br/><br/> [ServiceContract] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IRandomNumber</span> <br/> { <br/> [OperationContract(IsOneWay = <span class="hljs-literal">true</span>)] <br/> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Receive</span>(<span class="hljs-params">Guid requestId, </span></span><br/><span class="hljs-function"><span class="hljs-params"> <span class="hljs-keyword">double</span> randomNumber</span>)</span>; <br/> } <br/> [DataContract] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Calculation</span> <br/> { <br/> [DataMember] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Operand1 { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <br/><br/> [DataMember] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Operand2 { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <br/><br/> [DataMember] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Opertor { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <br/> } <br/><br/> [DataContract] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CalculationResult</span> <br/> { <br/> [DataMember] <br/> <span class="hljs-keyword">public</span> Calculation Calculation { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <br/><br/> [DataMember] <br/> <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Result { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <br/> }
Listing 2: Eine einfache Implementierung des Beispieldienstes
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CalculatorService</span> : <span class="hljs-title">ICalculatorService</span> </span><br/><span class="hljs-class"> </span>{ <br/> <span class="hljs-keyword">private</span> <span class="hljs-type">Dictionary</span>&lt;string, <span class="hljs-type">Func</span>&lt;<span class="hljs-type">Calculation</span>, <br/> <span class="hljs-type">CalculationResult</span>&gt;&gt; <span class="hljs-type">Operations</span> = new <br/> <span class="hljs-type">Dictionary</span>&lt;string, <span class="hljs-type">Func</span>&lt;<span class="hljs-type">Calculation</span>, <br/> <span class="hljs-type">CalculationResult</span>&gt;&gt;(); <br/><br/> <span class="hljs-keyword">public</span> <span class="hljs-type">CalculatorService</span>() <br/> { <br/> <span class="hljs-type">Operations</span>.<span class="hljs-type">Add</span>(<span class="hljs-string">"+"</span>, <span class="hljs-built_in">c</span> =&gt; new <span class="hljs-type">CalculationResult</span>() <br/> { <span class="hljs-type">Calculation</span> = <span class="hljs-built_in">c</span>, <span class="hljs-type">Result</span> = <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand1</span> + <br/> <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand2</span> }); <br/> <span class="hljs-type">Operations</span>.<span class="hljs-type">Add</span>(<span class="hljs-string">"-"</span>, <span class="hljs-built_in">c</span> =&gt; new <span class="hljs-type">CalculationResult</span>() <br/> { <span class="hljs-type">Calculation</span> = <span class="hljs-built_in">c</span>, <span class="hljs-type">Result</span> = <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand1</span> - <br/> <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand2</span> }); <br/> <span class="hljs-type">Operations</span>.<span class="hljs-type">Add</span>(<span class="hljs-string">"*"</span>, <span class="hljs-built_in">c</span> =&gt; new <span class="hljs-type">CalculationResult</span>() <br/> { <span class="hljs-type">Calculation</span> = <span class="hljs-built_in">c</span>, <span class="hljs-type">Result</span> = <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand1</span> * <br/> <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand2</span> }); <br/> <span class="hljs-type">Operations</span>.<span class="hljs-type">Add</span>(<span class="hljs-string">"/"</span>, <span class="hljs-built_in">c</span> =&gt; new <span class="hljs-type">CalculationResult</span>() <br/> { <span class="hljs-type">Calculation</span> = <span class="hljs-built_in">c</span>, <span class="hljs-type">Result</span> = <br/> <span class="hljs-built_in">c</span>.<span class="hljs-type">Operand1</span>/<span class="hljs-built_in">c</span>.<span class="hljs-type">Operand2</span> }); <br/> } <br/><br/> <span class="hljs-keyword">public</span> <span class="hljs-type">CalculationResult</span> <span class="hljs-type">Execute</span>(<span class="hljs-type">Calculation</span> <br/> calculation) <br/> { <br/> <span class="hljs-keyword">return</span> <span class="hljs-type">Operations</span>[calculation.<span class="hljs-type">Opertor</span>](<br/> calculation); <br/> } <br/><br/> <span class="hljs-keyword">public</span> void <span class="hljs-type">GenerateRandomNumbers</span>(<span class="hljs-type">Guid</span> requestId, <br/> int <span class="hljs-built_in">count</span>, int delayInMs) <br/> { <br/> <span class="hljs-keyword">var</span> receiver = <br/> <span class="hljs-type">OperationContext</span>.<span class="hljs-type">Current</span>.<span class="hljs-type">GetCallbackChannel</span><br/> &lt;<span class="hljs-type">IRandomNumber</span>&gt;(); <br/> <span class="hljs-type">Task</span>.<span class="hljs-type">Run</span>(async () =&gt; <br/> { <br/> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>; counter &lt; <span class="hljs-built_in">count</span>; <br/> counter++) <br/> { <br/> await <span class="hljs-type">Task</span>.<span class="hljs-type">Delay</span>(delayInMs); <br/> receiver.<span class="hljs-type">Receive</span>(requestId, <br/> new <span class="hljs-type">Random</span>().<span class="hljs-type">NextDouble</span>()); <br/> } <br/> }); <br/> } <br/> }
Die Tatsache, dass unser Dienst einen Rückrufkanal unterstützen soll, bedingt bestimmte Anforderungen an den Transport, dieser muss den Rückkanal nämlich bereitstellen können. Bei bidirektionalen Tranporten wie TCP ist das kein Problem, bei HTTP-Transporten funktioniert das nur unter bestimmten Prämissen [8].Lässt man sich aus dem WSDL des Dienstes einen Proxy generieren, so ist es erforderlich, bei der Instanzierung des Clients einen InstanceContext mit zu übergeben, der eine Implementierung des Callback-Kontrakts enthält. Dieser wird als Interface mit dem Namen ICalculatorServiceCallback importiert und enthält die Receive-Methode aus dem serverseitigen IRandomNumber-Kontrakt.
<span class="hljs-keyword">var</span> calulatorClient = <span class="hljs-keyword">new</span> <span class="hljs-type">CalculatorServiceClient</span>(
<span class="hljs-keyword">new</span> <span class="hljs-type">InstanceContext</span>(<span class="hljs-keyword">new</span> <span class="hljs-type">RandomNumberReceiver</span>()));
Der RandomNumberReceiver implementiert hier den Callback-Kontrakt und gibt die Zufallszahlen einfach auf der Konsole aus, wenn man sich vorher angemeldet hat:
<span class="hljs-selector-tag">calulatorClient</span><span class="hljs-selector-class">.GenerateRandomNumbers</span>(<span class="hljs-selector-tag">Guid</span><span class="hljs-selector-class">.NewGuid</span>(),
10, 5000);
Bitte in einer Reihe aufstellen – die Vergleichsprobanden
Anhand der Kriterien und des Beispieldienstes sollen nun verschiedene mögliche Alternativen für die WCF beleuchtet werden. Dabei gehen wir jedoch einen Schritt weiter als Microsoft und begnügen uns nicht mit gRPC allein.Wir starten unseren Vergleich mit drei Technologien, die der WCF sehr nahe sind. Die erste ist Core WCF [9]. Dabei handelt es sich um eine Community-Portierung von WCF für .NET Core, die von der .NET Foundation unterstützt wird. Hier wird zwar keine hundertprozentige Kompatibilität versprochen, aber doch in Aussicht gestellt, dass sich viele Anwendungen nur durch die Änderung eines Namensraums portieren lassen sollen. Mit dem IpcServiceFramework [10] steht eine leichtgewichtige Lösung für Named Pipes/TCP-Kommunikation bereit, die der WCF ebenfalls sehr ähnlich ist. Der dritte Proband in dieser Vergleichsrunde ist SoapCore [11]. Hierbei handelt es sich um eine Middleware für ein ASP.NET-Core-Web-API-Projekt. Hier bringt man .NET Core also quasi das WCF-Protokoll durch die Hintertüre bei. Dieser Ansatz ermöglicht es, semantisch zunächst beim RPC-Stil von WCF zu bleiben, die eigene Lösung aber technologisch bereits auf .NET Core und Web APIs vorzubereiten.Damit ist der Brückenschlag in Richtung ASP.NET Core getan, und somit geht es in der zweiten Runde dann mit einem Web API weiter. In dieser Runde soll auch ServiceStack den Ring betreten [12]. Dabei handelt es sich um ein sehr ausgereiftes Service-Framework, das schon lange als Alternative zu WCF und Web API auftritt. Es unterstützt demnach sowohl die Erstellung von RPC- als auch von RESTful-Diensten und passt somit gut in die hybride Vergleichsrunde.In der dritten Runde nehmen wir uns dann den Top-Kandidaten gRPC vor. In dieser letzten Runde sollen außerdem alle vorher dargestellten Ansätze noch einmal in der vollständigen Übersicht präsentiert werden.Fazit
WCF ist tot, es lebe – ja was denn eigentlich? Microsofts Vorschlag, auf gRPC zu setzen, erscheint angesichts der Mächtigkeit der WCF und deren vielfältiger Einsatzmöglichkeiten ein wenig zu pauschal. In den folgenden Teilen dieser Serie werden wir also einen Blick auf einige weitere mögliche Alternativen werfen.Fussnoten
- Scott Hunter, .NET Core is the Future of .NET, http://www.dotnetpro.de/SL1911WCF1
- Richard Lander, Introducing .NET 5, http://www.dotnetpro.de/SL1911WCF2
- Scott Hunter, Scott Hanselman, .NET Platform Overview and Roadmap (bei 54:26), http://www.dotnetpro.de/SL1911WCF3
- Martin Fowler, Richardson Maturity Model, http://www.dotnetpro.de/SL1911WCF4
- WSDL 2.0, Core Language, http://www.dotnetpro.de/SL1911WCF5
- SOAP 1.2, Messaging Framework, http://www.dotnetpro.de/SL1911WCF6
- Quellcode zum Artikel auf GitHub, http://www.dotnetpro.de/SL1911WCF7
- Microsoft Docs, WSDualHttpBinding Class, http://www.dotnetpro.de/SL1911WCF8
- Core WCF Project auf GitHub, http://www.dotnetpro.de/SL1911WCF9
- IpcServiceFramework auf GitHub, http://www.dotnetpro.de/SL1911WCF10
- SOAP extension for ASP.NET Core auf GitHub, http://www.dotnetpro.de/SL1911WCF11
- ServiceStack auf GitHub, http://www.dotnetpro.de/SL1911WCF12
- gRPC, https://grpc.io