WCF-Alternativen (Teil 3) – Eine Anleitung zur Migration von WCF zu gRPC

In diesem Blogpost der Artikelserie zu den Alternativen der Windows Communication Foundation (WCF) werden die Besonderheiten und Herausforderungen einer WCF-Migration als Vorbereitung zu einer späteren Portierung der Anwendung auf .NET Core beschrieben.

Nachdem im letzten Artikel ASP.NET Core Web API als Alternative vorgestellt wurde, wird in diesem Beitrag auf gRPC als eine weitere Möglichkeit eingegangen. Auch hier soll Schritt für Schritt ein mögliches Vorgehen bei der Migration von WCF zu gRPC beschrieben werden.

Vorgehen bei der Migration

In der Regel existiert ein separates WCF-Projekt in der Solution. Da eine direkte Umstellung nicht möglich ist, kann dieses Projekt zunächst unverändert in der Solution verbleiben.

Zunächst sollte ein neues Class-Library-Projekt für Shared-Objekte zwischen Server und Client angelegt werden. In dieses Projekt werden die ServiceContract Interfaces sowie die DataContract-Klassen aus dem WCF-Projekt kopiert und die WCF-spezifischen Attribute wie „ServiceContract“, „OperationContract“, „DataContract“, „DataMember“ usw. entfernt.

Client-Projekt

Im WCF Service konsumierenden Projekt wird als erstes die WCF-Service-Referenz entfernt. Auch können die WCF-spezifischen Attribute wie „CallbackBehavior“ o. Ä. entfernt werden.

Eine neue Referenz zum zuvor angelegten Class-Library-Projekt für die Shared-Objekte wird hinzugefügt. Als nächstes kann im Client-Projekt eine leere Implementierung des jetzt im Class-Library-Projekt abgelegten ServiceContract Interfaces erstellt werden. Die „alte“ Initialisierung des WCF Service wird jetzt auf die noch leere Implementierung des ServiceContract geändert.

Abschließend müssen noch die Usings für die zuvor verwendeten DataContract-Klassen aus dem WCF Service auf das neue Class-Library-Projekt geändert werden. Damit sollte sich das Client-Projekt wieder kompilieren lassen. Um das Projekt auch wieder starten zu können, ist der Teil <system.serviceModel> aus der *.config zu entfernen.

Erstellung der Schnittstellenbeschreibung mit Protocol Buffers

Bei gRPC wird die Schnittstelle mit der Protocol Buffer Language in *.proto-Dateien beschrieben. Am besten wird die *.proto-Datei im neu angelegten Class-Library-Projekt hinzugefügt. Um später daraus Server- und Client-Klassen generieren zu können, müssen zusätzlich die NuGet-Pakete „Google.Protobuf“, „Grpc.Core“ und „Grpc.Tools“ hinzugefügt werden.

Nach Anlage der *.proto-Datei muss diese in der *.csproj-Datei im Knoten „ItemGroup“ durch folgende Zeile bekannt gemacht werden.

<Project Sdk="Microsoft.NET.Sdk">
 
  <ItemGroup>
    <Protobuf Include="ProtoZeitService.proto" GrpcServices="Both" />
 
  </ItemGroup>
 
</Project>

Definition der *.proto in *.config Datei


Aufbau der *.proto Datei

Nachfolgend ist ein Beispiel zu sehen, wie eine WCF-Service-Beschreibung in eine *.proto-Datei überführt werden kann.

Die [ServiceContract]-Attribute werden zu „Service“ und aus [OperationContract] werden „rpc“-Aufrufe. Die als [DataContract] gekennzeichneten Klassen werden zu „message“-Objekten.

[ServiceContract]
public interface IDataInputService
{
    [OperationContract]
    int CreateUser(User user);
 
    [OperationContract]
    int Login(User user);
 
    [OperationContract]
    List<Time> GetTimes(int userId);
 
    [OperationContract]
    void AddTime(Time time, int userId);
 
    [OperationContract]
    List<string> Projects();
}
 
[DataContract]
public class User
{
    [DataMember]
    public string Name { get; set; }
 
    [DataMember]
    public string Passwort { get; set; }
}
 
[DataContract]
public class Time
{
    [DataMember]
    public DateTime Start { get; set; }
 
    [DataMember]
    public DateTime End { get; set; }
 
    [DataMember]
    public string Project { get; set; }
 
    [DataMember]
    public int uId { get; set; }
 
    [DataMember]
    public int Id { get; set; }
}

Beispiel eines zu migrierenden WCF ServiceContract und DataContract


syntax = "proto3";
 
import "google/protobuf/timestamp.proto";
import "google/protobuf/Empty.proto";
 
option csharp_namespace = "DataInputt.ZeitService.Api";
 
service DataInputService {
    rpc CreateUser (UserDto) returns (UserResponse) {}
    rpc Login (UserDto) returns (UserResponse) {}
    rpc GetTimes (GetTimesRequest) returns (TimeCollection) {}
    rpc AddTime (AddTimeRequest) returns (google.protobuf.Empty) {}
    rpc Projects (google.protobuf.Empty) returns (ProjectCollection) {}
}
 
 
message UserDto {
    string name = 1;
    string passwort = 2;
}
 
message TimeDto {
    google.protobuf.Timestamp start = 1;
    google.protobuf.Timestamp end = 2;
    string project = 3;
    int32 uid = 4;
    int32 id = 5;
}
 
message UserResponse {
    int32 id = 1;
}
 
message GetTimesRequest {
    int32 userId = 1;
}
 
message TimeCollection {
    repeated TimeDto times = 1;
}
 
message AddTimeRequest {
    TimeDto time = 1;
    int32 userId = 2;
}
 
message ProjectCollection {
    repeated string projects = 1;
}

Beispiel der erstellten gRPC *.proto-Datei


Bei der Erstellung der *.proto-Datei sollten folgende Punkte berücksichtigt werden.

Angabe Namespace

Damit die generierte Server- und Client-Implementierung den korrekten Namespace erhält, sollte dieser in der *.proto-Datei angegeben werden.

Definition Übergabe/Rückgabe Parameter

Bei gRPC-Schnittstellen sind nur Aufrufe mit einem einzigen Paramater zugelassen. Wird im WCF Service mit mehreren Übergabeparametern gearbeitet, müssen diese in einem neuen Message-Objekt zusammengefasst werden.

Jeder Aufruf einer gRPC-Schnittstelle muss auch einen Rückgabewert haben. Gab es im WCF Service void-Methoden, so müssen diese bei gRPC jetzt den speziellen Typ „google.protobuf.Empty“ zurückgeben.

Des Weiteren darf für die Übergabe und Rückgabe kein einzelner primitiver Datentyp (int, bool, string) verwendet werden. Soll für die Rückgabe nur ein int oder string verwendet werden, so muss auch dafür ein extra Message-Objekt erstellt werden.

Rufen sich im WCF Service Methoden gegenseitig auf, war das bei der Verwendung eines primitiven Datentyps sehr einfach. Soll das auch in der gRPC-Schnittstelle möglich sein, ist darauf zu achten, dass die betreffenden Methoden dieselben Message-Objekte verwenden. So kann ein unnötiges Mapping vermieden werden.

Bezeichnung der Message-Objekte

Bei der Bezeichnung der Message-Objekte sollten diese besser nicht 1:1 von den DataContract-Klassen des WCF Service übernommen werden. Der Grund dafür ist, dass die später aus der Definition generierten C#-Klassen teilweise andere Datentypen verwenden und für die Verwendung erst gemappt werden müssen. Um diese dann besser von den DataContract-Klassen zu trennen, empfiehlt sich hier eine differenzierte Bezeichnung.

Außerdem müssen die Properties innerhalb der Message-Objekte fortlaufend nummeriert werden.

Datentypen in Message-Objekten

Aus den Message-Objekten der *.proto-Datei werden automatisch C#-Klassen generiert. Hier sollte man sich bewusst sein, dass nicht immer die Standard-C#-Datentypen für die Generierung verwendet werden.

So wird aus dem in der *.proto-Datei angegebenen Typ google.protobuf.Timestamp in der C#-Klasse der Typ Google.Protobuf.WellKnownTypes.Timestamp, welcher bei Verwendung immer erst in ein DateTime umgewandelt werden muss.

Wird in der *.proto-Datei „repeated“ angegeben, wird daraus kein List<T> sondern ein Google.Protobuf.Collections.RepeatedField<T>, welches auch entsprechend gemappt werden muss.

Auch andere Typen wie z. B. Dictionary<K, V> haben in der *.proto-Datei und der später generierten C#-Klasse jeweils andere Typen. Der C#-Typ „decimal“ wird aktuell aufgrund fehlender Rundungsgenauigkeit von der *.proto-Datei noch gar nicht unterstützt. Als Workaround wird empfohlen, sich ein eigenes Decimal-Message-Objekt zu erstellen, welches Vor- und Nachkommastellen als separate int-Werte abbildet.

Anlage des gRPC-Server-Projekts

Das gRPC-Server-Projekt kann als einfache Konsolenanwendung erstellt werden und sollte einen Verweis auf das zuvor neu angelegte Class-Library-Projekt mit der *.proto-Datei haben.

Um den Server zu starten, sind nur wenige Zeilen Code nötig:

static void Main(string[] args)
{
    const int port = 9000;
    const string host = "0.0.0.0";

    Grpc.Core.Server server = new Grpc.Core.Server
    {
        Services = { DataInputt.ZeitService.Api.ZeitService.BindService(new ZeitService()) },
        Ports = { new Grpc.Core.ServerPort(host, port, Grpc.Core.ServerCredentials.Insecure) }
    };
    server.Start();

    Console.WriteLine($"Starting server {host}:{port}");
    Console.WriteLine("Press any key to stop...");
    Console.ReadKey();
}

Beispiel für den Start eines gRPC Server


Es müssen lediglich Host und Port definiert sowie dem im Class-Library-Projekt durch die *.proto-Datei generierten Service eine Implementierung zugewiesen werden. Dabei sollte die Implementierung im gRPC-Server-Projekt liegen.

Implementierung des gRPC Service

Die Implementierung des gRPC Service erfolgt durch das Erben der im Class-Library-Projekt durch die *.proto-Datei generierten ServiceBase. Die einzelnen Serviceaufrufe können dann durch ein override implementiert werden.

public class ZeitService : DataInputt.ZeitService.Api.ZeitService.ZeitServiceBase
{
    public override Task<UserResponse> CreateUser(UserDto request, ServerCallContext context)
    {
         
    }
 
    public override Task<UserResponse> Login(UserDto request, ServerCallContext context)
    {
         
    }
 
    public override Task<TimeCollection> GetTimes(GetTimesRequest request, ServerCallContext context)
    {
         
    }
 
    public override Task<Empty> AddTime(AddTimeRequest request, ServerCallContext context)
    {
         
    }
 
    public override Task<ProjectCollection> Projects(Empty request, ServerCallContext context)
    {
         
    }   
}

Beispiel für die Server-Implementierung eines gRPC Service


Wird für die Service-Implementierung der „alte“ WCF Code verwendet, kann ein Mapping der Parameter nötig sein, wenn die Datentypen nicht zu den „alten“ DataContract-Klassen passen.

Wichtig ist auch zu wissen, dass sich der Lifecycle der Service-Implementierung über die gesamte Laufzeit des gRPC Service erstreckt (Singelton). Anders als bei einem Web API Controller wird nicht für jeden Request eine neue Instanz der Service-Implementierung erstellt. Somit bleibt der Zustand des gRPC Service zwischen den Aufrufen erhalten. Klassenvariablen und im Konstruktor erstellte oder injizierte Ressourcen sollten daher besser vermieden werden, da deren Zustand sonst zwischen den Aufrufen ggf. nicht sichergestellt werden kann.

Implementierung des gRPC Clients

Für die Implementierung des gRPC Clients kann die im konsumierenden Projekt angelegte leere Implementierung des ServiceContract Interfaces genutzt werden. Hier muss zunächst eine Verbindung zum gRPC Server hergestellt werden.

const int port = 9000;
string host = Environment.MachineName;
 
var channel = new Channel(host, port, ChannelCredentials.Insecure);
var grpcClient = new ZeitService.Api.ZeitService.ZeitServiceClient(channel);

Beispiel eines Clients zum Herstellen der Verbindung zum gRPC Server


Auch hier kommt eine aus der im Class-Library-Projekt durch die *.proto-Datei generierte Client-Klasse zum Einsatz. Durch diese werden die in der *.proto-Datei definierten Aufrufe bereitgestellt.

Jetzt muss die leere Implementierung des ServiceContract Interfaces durch die entsprechenden Aufrufe der gRPC-Client-Klasse ergänzt werden. Auch hier kann es nötig sein, die vom gRPC Service verwendeten Über- und Rückgabeparameter auf die bisherigen DataContract-Klassen zu mappen.

Durch die Verwendung und Implementierung des „alten“ WCF Service Interface muss im konsumierenden Projekt nichts weiter angepasst und geändert werden.

Bidirektionale Kommunikation

Das Konzept der bidirektionalen Kommunikation in gRPC unterscheidet sich stark von WCF Duplex Services.

Bei WCF kann der Server über Callback Interfaces sehr einfach verschiedene Methoden auf Client-Seite aufrufen. Hingegen wird bei gRPC vom Client ausgehend eine Server-Methode angesprochen, welche als Stream Daten an den Client zurückliefert.

Dazu muss die gRPC-Server-Methode so implementiert werden, dass diese nicht beendet und somit die Verbindung aufrechterhalten wird. Anschließend kann zum Beispiel durch Events die Übertragung von Daten an den Client ausgelöst werden.

Auch beim Client muss nach dem Aufruf der Server-Methode die Verbindung aufrechterhalten und auf den Empfang neuer Daten reagiert werden.

Somit sind für eine Umstellung auf gRPC Streaming sehr rundlegende und konzeptionelle Anpassungen nötig.

Nicht betrachtet wurden Querschnittsfunktionen wie Authentifizierung, Autorisierung, Logging und Fehlerbehandlung der gRPC-Aufrufe – diese Punkte sollten im Einzelfall geprüft und ggf. angepasst werden.

Fazit

Die Umstellung von WCF auf gRPC ist verglichen mit ASP.NET Core Web API deutlich aufwendiger und mit mehr Code-Anpassungen verbunden. Zunächst muss eine *.proto-Datei erstellt werden. Durch die Vorgabe, dass jeder Serviceaufruf eine Rückgabe haben muss und max. ein Übergabeparamater zulässig ist, sind teilweise Anpassungen an den Methodensignaturen nötig. Da in den generierten Klassen teilweise keine .NET-Standard-Typen verwendet werden, muss jede Server- und Client-Methode mit dem entsprechenden Mapping-Code ergänzt werden.

Bei der Verwendung von gRPC sollte auch unbedingt berücksichtigt werden, dass sich der Lifecycle der Serviceinstanz über die gesamte Ausführungszeit des gRPC Servers erstreckt (Singelton).

WCF-Alternativen (Teil 2) – Eine Anleitung zur Migration von WCF zu Web API

In diesem Blogpost der Artikelreihe zu den Alternativen der Windows Communication Foundation (WCF) werden die Besonderheiten und Herausforderungen einer WCF-Migration als Vorbereitung zu einer späteren Portierung der Anwendung auf .NET Core beschrieben.

In diesem Beitrag wird dabei zunächst auf ASP.NET Core Web-API als eine mögliche Alternative eingegangen und Schritt für Schritt beschrieben, wie eine mögliche Migration von WCF zu ASP.NET Core Web API aussehen kann. Wie dies dagegen bei der Migration zu gRPC funktioniert, lest ihr im nächsten Blogartikel.

Vorgehen bei der Migration

In der Regel existiert ein separates WCF-Projekt in der Solution. Da eine direkte Umstellung nicht möglich ist, kann dieses Projekt zunächst unverändert in der Solution verbleiben.

Zunächst sollte ein neues Class-Library-Projekt für Shared-Objekte zwischen Server und Client angelegt werden. In dieses Projekt werden die ServiceContract Interfaces sowie die DataContract-Klassen aus dem WCF-Projekt kopiert und die WCF-spezifischen Attribute wie „ServiceContract“, „OperationContract“, „DataContract“, „DataMember“ usw. entfernt.

Client Projekt

Im WCF Service konsumierenden Projekt wird als erstes die WCF-Service-Referenz entfernt. Auch können die WCF-spezifischen Attribute wie „CallbackBehavior“ o. Ä. entfernt werden.

Eine neue Referenz zum zuvor angelegten Class-Library-Projekt für die Shared-Objekte wird hinzugefügt. Als nächstes kann im Client-Projekt eine leere Implementierung des jetzt im Class-Library-Projekt abgelegten ServiceContract Interfaces erstellt werden. Die „alte“ Initialisierung des WCF Service wird jetzt auf die noch leere Implementierung des ServiceContract geändert.

Abschließend müssen noch die Usings für die zuvor verwendeten DataContract-Klassen aus dem WCF Service auf das neue Class-Library-Projekt geändert werden. Damit sollte sich das Client-Projekt wieder kompilieren lassen. Um das Projekt auch wieder starten zu können, ist der <system.serviceModel> aus der *.config zu entfernen.

Web-API-Projekt

Für den neuen Server wird ein „Standard“ ASP.NET Core Web-API-Projekt angelegt und eine Referenz auf das neue Class-Library-Projekt hinzugefügt. Dies ist nötig, damit der Server dieselben Austauschklassen (vorher DataContract) wie der Client verwenden kann.

Für jeden bisherigen WCF ServiceContract wird nun in der Web-API ein Controller angelegt. Die Implementierung kann zum Einstieg aus dem „alten“ WCF Service übernommen werden. Danach müssen die Rückgabewerte auf ActionResult bzw. ActionResult<T> geändert werden. Es müssen die [Http..] Verb Attribute sowie eine Route für jede Methode angegeben werden. Auch sollte das [ProducesResponseType]-Attribut bei jeder Methode angegeben werden – dieses beschreibt den zu erwartenden Rückgabewert und wird später für die Generierung des Clients verwendet.

[ServiceContract(CallbackContract = typeof(IDataCallback))]
public interface IDataInputService
{
    [OperationContract]
    int CreateUser(User user);

    [OperationContract]
    int Login(User user);

    [OperationContract]
    List<Time> GetTimes(int userId);

    [OperationContract]
    void AddTime(Time time, int userId);

    [OperationContract]
    List<string> Projects();
}

Beispiel eines zu migrierenden WCF Service Contract


[Route("api/zeitservice")]
[ApiController]
public class ZeitServiceController : ControllerBase
{    
    [HttpPost("CreateUser")]
    [ProducesResponseType(typeof(int), 200)]
    public ActionResult<int> CreateUser(User user)
    {
 
    }

    [HttpPost("Login")]
    [ProducesResponseType(typeof(int), 200)]
    public ActionResult<int> Login(User user)
    {
		
    }

    [HttpGet("Times")]
    [ProducesResponseType(typeof(List<Time>), 200)]
    public ActionResult<List<Time>> GetTimes(int userId)
    {
        
    }

    [HttpPost("AddTime")]
    [ProducesResponseType(200)]
    public ActionResult AddTime(Time time, int userId)
    {
        
    }

    [HttpGet("Projects")]
    [ProducesResponseType(typeof(List<string>), 200)]
    public ActionResult<List<string>> Projects()
    {
        
    }
}

Beispiel des erstellten Web-API-Controllers


Hinweis: Der auf diese Weise erstellte Web API Controller entspricht vermutlich nicht einer ressourcenorientierten REST API. Die API ist eher actionbasiert und spiegelt den „alten“ Service wider – was für die Umstellung aber zunächst in Ordnung ist.

Client-Generierung

Um für den Aufruf und die Verwendung der Web API möglichst wenig Code schreiben zu müssen, kann dieser generiert werden. Hierzu kann die OpenAPI-Spezifikation (vormals Swagger) verwendet werden.

Um die OpenAPI-Spezifikation und darauf basierend den Client Code zu generieren, gibt es verschiedene Möglichkeiten. Nachfolgend wird eine der Optionen beispielhaft beschrieben.

Damit die OpenAPI-Spezifikation automatisch generiert werden kann, wird zunächst das NuGet-Paket „NSwag.AspNetCore“ eingebunden und nach der Anleitung Getting started with NSwag im Web-API-Projekt konfiguriert. Danach kann durch den Aufruf der URL /swagger/ bei gestartetem Web-API-Projekt die API-Schnittstelle bereits im Browser getestet werden.

Übersicht Swagger
Abbildung 1: Übersicht Swagger
Detailansicht Swagger
Abbildung 2: Detailansicht Swagger

Der Client Code für den Zugriff auf die neue Web-API kann mit dem NSwagStudio generiert werden. In den Einstellungen des Generators sollte dabei auf die korrekte Angabe des Namespace für die Generierung des Clients geachtet werden. Eventuell sind projektspezifisch weitere Einstellungen nötig, bis das gewünschte Ergebnis generiert wird. Der vom Generator erstellte Client sowie die im Generator vorgenommen Einstellungen (*.nswag-Datei) sollten im Projekt abgelegt werden.

NSwag-Studio-Generator
Abbildung 3: NSwag-Studio-Generator

Client-Verwendung

Wird vom Generator das gewünschte Ergebnis generiert, ist zur Einbindung des neuen Clients nur noch eine Änderung nötig. Es muss lediglich die zuvor erstellte, noch leere Dummy-Implementierung des ServiceContract vom neu generierten Client erben und die leere Implementierung entfernt werden. Dabei muss jedoch sichergestellt werden, dass der neu generierte Client das ServiceContract Interface erfüllt.

Damit ist die Migration von WCF zu Web API bereits abgeschlossen und sollte nun getestet werden.

Bidirektionale Kommunikation

Wurde im WCF Service bidirektionale Kommunikation genutzt, muss diese jetzt über die Verwendung von SignalR realisiert werden.

Hierzu wird im Web-API-Projekt für jeden WCF CallbackContract eine Hub-Klasse angelegt, welche von Microsoft.AspNetCore.SignalR.Hub erbt. In der Startup.cs des Web-API-Projekts muss in der Methode ConfigureServices die Zeile „services.AddSignalR();“ hinzugefügt werden. Bei der Methode Configure wird bei der Definition von UseEndpoints je Hub ein Mapping hinterlegt „endpoints.MapHub<EarningsHub>(„/Earnings“);“.

public class Startup
{    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {       
        app.UseEndpoints(endpoints =>
        {            
            endpoints.MapHub<EarningsHub>("/Earnings");
        });
    }
}

public class EarningsHub : Microsoft.AspNetCore.SignalR.Hub
{
	
}

Änderungen an der Web-API-Startup Klasse und Definition des SignalR Hub


Durch Injection von IHubContext<EarningsHub> kann mit nur einer Zeile das Senden von Daten an alle oder nur spezifische Clients ausgelöst werden.

[Route("api/zeitservice")]
[ApiController]
public class ZeitServiceController : ControllerBase
{
    private readonly IHubContext<EarningsHub> earningsHubContext;
    public ZeitServiceController(IHubContext<EarningsHub> earningsHubContext)
    {
        this.earningsHubContext = earningsHubContext;
    }

    [HttpPost("AddTime")]
    [ProducesResponseType(200)]
    public ActionResult AddTime(Time time, int userId)
    {
        Task.Run(async () => await earningsHubContext.Clients.All.SendAsync("EarningsCalculated", result)).GetAwaiter().GetResult();
    }
}

Verwendung des SignalR Hub im API-Controller


Anschließend wird im konsumierenden Projekt zunächst das NuGet-Paket „Microsoft.AspNetCore.SignalR.Client“ hinzugefügt. Wie im nachfolgenden Beispiel zu sehen ist, muss die bisher vom Server aufgerufene Methode gar nicht geändert werden. Bei WCF wurde die Methode durch Verwendung des Callback Interface und deren Zuweisung bei der Initialisierung des Clients durch „new InstanceContext(this)“ ausgelöst. Bei der SignalR Implementierung wird eine Verbindung zum Hub aufgebaut und das auslösende Server Event an die bestehende Methode gebunden.

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)]
public partial class Zeiterfassung : Page, IDataInputServiceCallback
{
    private DataInputServiceClient client;
    
    public Zeiterfassung()
    { 
        client = new DataInputServiceClient(new InstanceContext(this));
    }

    public void EarningsCalculated(Dictionary<int, decimal> earnings)
    {
        // Client Methode welche vom Server aufgerufen wird
    }
}

[ServiceContract(CallbackContract = typeof(IDataCallback))]
public interface IDataInputService
{
    
}

public interface IDataCallback
{
    [OperationContract(IsOneWay = true)]
    void EarningsCalculated(IDictionary<int, decimal> earnings);
}

Client Beispiel Verwendung des WCF CallbackContract


public partial class Zeiterfassung : Page
{
    private HubConnection connection;
    
    public Zeiterfassung()
    {    
        connection = new HubConnectionBuilder().WithUrl("http://localhost:52841/Earnings").Build();
        connection.On<Dictionary<int, decimal>>("EarningsCalculated", EarningsCalculated);
        connection.StartAsync();
    }
	
    public void EarningsCalculated(Dictionary<int, decimal> earnings)
    {
        // Client Methode welche vom Server aufgerufen wird
    }
}

Client Beispiel nach Umstellung auf SignalR


Die hier beispielhafte Verwendung von SignalR ist sehr einfach gehalten. Für einen produktiven Einsatz sollte eine robuste Implementierung mit automatischem Reconnect usw. sichergestellt werden – siehe dazu auch: https://docs.microsoft.com/de-de/aspnet/core/signalr/dotnet-client?view=aspnetcore-3.1&tabs=visual-studio

Nicht betrachtet wurden bei der Migration Querschnittsfunktionen wie Authentifizierung, Autorisierung, Logging und Fehlerbehandlung der Web-API-Aufrufe – diese Punkte sollten im Einzelfall geprüft und ebenfalls angepasst werden.

Fazit

Die Umstellung von WCF auf ASP.NET Core Web API ist mit überschaubarem Aufwand möglich. Die Implementierung der WCF Services kann mit kleinen Anpassungen an Rückgabewerten und Attributen direkt in die neuen Web API Controller übernommen werden. Durch die Verwendung der OpenAPI-Spezifikation kann ein Client für den Zugriff auf die Web API generiert werden, welcher das bisherige WCF ServiceContract Interface unterstützt. So müssen in der konsumierenden Anwendung lediglich ein paar Usings und die Initialisierung des Clients angepasst werden.