Architektur-Modernisierung: Migration von WCF zu gRPC mit ASP.NET Core – ein pragmatischer Ansatz

Viele Projekte mit verteilten Anwendungen in der .NET-Welt basieren noch auf der Windows Communication Foundation (WCF). Doch wie kommt man weg von der "Altlast" und wie stellt man seinen Code auf sowohl moderne als auch zukunftssichere Beine? Eine mögliche Lösung ist gRPC.

In diesem Artikel:

Versionsinformation:

  • ASP.NET Core 7.0
  • WCF mit .NET 4.8

Den Beispielcode für diesen Artikel finden Sie hier

Einleitung

Der Wechsel von WCF zu gRPC ist ein Schritt, den Projekt-Teams anstreben können, um ihre verteilten Anwendungen zu modernisieren und zu optimieren. WCF-Dienste bieten umfassende Unterstützung für verschiedene Transportprotokolle und Sicherheitsmechanismen. gRPC, ein von Google entwickeltes Open-Source-Framework, verwendet HTTP/2 als Transportprotokoll und verwendet Protocol Buffers (Protobuf) als Standard-Nachrichtenformat, welches höhere Geschwindigkeit und bessere Skalierbarkeit verspricht.

Dieser Artikel stellt einen praktischen und pragmatischen Ansatz zum Migrieren vorhandener WCF-Dienste zu gRPC-Diensten mit ASP.NET Core vor und enthält die notwendigen konkreten Schritte, die zum Planen und Umsetzen der Migration erforderlich sind.

Migration mit gRPC Code-First

Im Standardvorgehen verwendet gRPC einen Contract-First-Ansatz mit Protocol Buffers als Nachrichten- und Contract-Format. Protobuf ist eine schnelle und effiziente Möglichkeit, Daten zwischen verschiedenen Systemen auszutauschen. Zudem erlaubt es die Definition eines Schnittstellen-, Nachrichten- und Datenkontrakts für Kommunikation zwischen verschiedenen Systemen, indem es ermöglicht wird, Service-Methoden und zugehörige Parameter in einer interoperablen Art und Weise zu definieren. Allerdings müsste man dann alle bestehenden Service-Schnittstellen und Modelle in Protobuf beschreiben, was insbesondere bei komplexen Anwendungen einen hohen Aufwand bedeuten kann. Und man hat ja bei existierenden WCF-Lösungen oft .NET sowohl auf der Client- als auch auf der Services-Seite. Um daher den Aufwand bei der Migration zu verringern, gibt es ein Community-Projekt namens protobuf-net.Grpc, das es ermöglicht bestehende WCF-Dienste mittels Code-First-Ansatz in gRPC Services zu refactoren. Dies erlaubt das Schreiben von .NET-Code für die Nutzung von gRPC und die Wiederverwendung existierenden Codes als ersten Schritt zur Modernisierung bestehender Applikationen.

Voraussetzungen für WCF-Dienste

Damit ein bestehender WCF Service als gRPC Code-First Dienst genutzt werden kann, muss der Contract bestimmte Anforderungen erfüllen:

1. Auf dem Contract muss das ServiceContractAttribute gesetzt sein
2. Alle Service-Methoden müssen das OperationContractAttribute haben
3. Alle Service-Methoden dürfen nur ein Argument haben und dieses muss eine Klasse/DTO sein. Gleiches gilt auch für den Rückgabetypen, diese müssen vom Typ void, Task oder eine eigene Klasse/DTO sein
4. Alle DTOs müssen das DataContractAttribute haben
5. Alle Properties auf dem DTO müssen das DataMemberAttribute haben und dies muss eine eindeutige Order haben, damit der richtige Datenfluss garantiert ist.

Im Folgenden sieht man ein Beispiel, wie ein existierender Contract refactored werden muss, damit dieser mit gRPC Code-First nutzbar ist.

				
					[ServiceContract]
public interface IGreeter
{
    [OperationContract]
    public string Greet(string firstName, string lastName);
}
				
			

Dieser Service-Contract erfüllt nicht alle Anforderungen, um als gRPC Code-First Dienst verwendet zu werden, da er mehrere Parameter entgegennimmt. Die Methode kann jedoch leicht angepasst werden, indem zwei DTO-Klassen erstellt werden, welche zum einen die Parameter und zum anderen die Rückgabe enthalten. Zum Beispiel:

				
					[DataContract]
public class GreetRequest
{
    [DataMember(Order = 1)]
    public string FirstName { get; set; }

    [DataMember(Order = 2)]
    public string LastName { get; set; }
}

[DataContract]
public class GreeterResponse
{
    [DataMember(Order = 1)]   
    public string Response { get; set; }
}

[ServiceContract]
public interface IGreeter
{
    [OperationContract]
    public GreeterResponse Greet(GreetRequest request);
}
				
			

gRPC-Dienst in ASP.NET Core hosten

Wenn der Contract alle Anforderungen erfüllt, kann der Dienst mittels des Pakets protobuf-net.Grpc.AspNetCore in ASP.NET Core gehostet werden. Folgende Schritte sind notwendig:

1. Das Nuget-Paket hinzufügen. Dafür kann die Command Line oder der Nuget-Paket-Manager der IDE genutzt werden bzw. die csproj-Datei angepasst werden:

				
					<ItemGroup>
    <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.1.1" />
</ItemGroup>
				
			

2. Die gRPC Code-First Dienste müssen in der Dependency Injection (DI) registriert werden. Hierfür muss die Methode AddCodeFirstGrpc aufgerufen werden.

				
					builder.Services.AddCodeFirstGrpc();
				
			

3. Die Service-Implementierung (GreeterService) muss in die ASP.NET Core Pipeline hinzugefügt werden, um ihn erreichbar zu machen.

				
					app.MapGrpcService<GreeterService>();
				
			

Das vollständige Beispiel sieht dann so aus:

				
					using ProtoBuf.Grpc.Server;
using Service;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCodeFirstGrpc();

var app = builder.Build();
app.MapGrpcService<GreeterService>();
app.Run();
				
			

Damit ist der Dienst in der ASP.NET-Core-Anwendung registriert und erreichbar.

gRPC Code-First Clients

Um einen Client für einen gRPC Code-First Dienst in C# zu erstellen, müssen zuerst die benötigten NuGet-Pakete installiert werden. Die Pakete Grpc.Net.Client und protobuf-net.Grpc enthalten die erforderlichen Klassen, um einen gRPC-Client zu erstellen.

				
					<ItemGroup>
    <PackageReference Include="Grpc.Net.Client" Version="2.51.0" />
    <PackageReference Include="protobuf-net.Grpc" Version="1.1.1" />
</ItemGroup>
				
			

Anschließend muss eine Verbindung zum Dienst hergestellt und eine Client-Instanz erstellt werden. Dazu kann die GrpcChannel-Klasse und die Methode CreateGrpcService mit dem Service Contract verwendet werden. Dies ist die Power des Code-First-Ansatzes für gRPC. WCF-Entwickler werden hier sicherlich die ChannelFactory aus den alten Zeiten wiedererkennen.

Sobald die Client-Instanz erstellt ist, kann die gRPC-Methode über die entsprechende Client-Methode aufgerufen werden.

Hier ist ein Beispiel für die Erstellung eines Clients für den GreeterService:

				
					using Grpc.Net.Client;
using ProtoBuf.Grpc.Client;
using Service;

using var channel = GrpcChannel.ForAddress("https://localhost:7199");
var client = channel.CreateGrpcService<IGreeter>();
var reply = client.Greet(new GreetRequest() { FirstName = "John", LastName = "Doe"});
Console.WriteLine($"Greeting: {reply.Response}");
				
			

WCF Behaviors in gRPC nachstellen

WCF Behaviors ermöglichen zusätzliche Verarbeitungsfunktionen für eingehende und ausgehende Nachrichten in einem WCF-Dienst. Diese Funktionalität kann in gRPC mit Hilfe von ASP.NET Core Middlewares  und gRPC Interceptors  nachgebildet werden. Eine Middleware kann zum Beispiel genutzt werden, um Authentifizierung und Autorisierung zu implementieren, während Interceptors dazu dienen können, eingehende und ausgehende Nachrichten zu überwachen, zu ändern oder zu blockieren.

Zur Registrierung von ASP.NET Core Middlewares kann die Use-Methode der IApplicationBuilder-Schnittstelle aufgerufen werden. So kann mit folgendem Beispiel Authentifizierung für einen gRPC-Dienst hinzugefügt werden.

				
					..
builder.Services
    .AddAuthentication()
    .AddJwtBearer(options =>
    {
        // TODO: JWT Bearer Authentifizierung konfigurieren
    });
....
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGrpcService<GreeterService>();
....
				
			

Mit dem AddInterceptor-Methodenaufruf auf den GrpcServiceOptions können gRPC-Interceptors konfiguriert werden, um benutzerdefinierten Code für die Verarbeitung von eingehenden und ausgehenden Nachrichten zu registrieren. Interceptors können global für alle Dienste oder aber pro Dienst festgelegt werden. Ein Beispiel für einen globalen Interceptor, der eingehende Nachrichten logged, kann so aussehen:

				
					public class LoggingInterceptor : Interceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    {
        _logger = logger;
    }
    
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        _logger.LogDebug("Aufruf der gRPC-Methode {method} mit Argument {argument}", context.Method, request);
        var result = await base.UnaryServerHandler(request, context, continuation);
        _logger.LogDebug("gRPC-Methode {method} mit Ergebnis {result} wurde aufgerufen", context.Method, result);
        return result;
    }
}
				
			

Um den Interceptor zu registrieren, muss die entsprechende AddInterceptor-Method aufgerufen werden.

				
					// global
builder.Services
    .AddCodeFirstGrpc(options => options.Interceptors.Add<LoggingInterceptor>())

// per Service
builder.Services
    .AddCodeFirstGrpc(_ => { })
    .AddServiceOptions<GreeterService>(options => options.Interceptors.Add<LoggingInterceptor>());
				
			

Durch die Verwendung von ASP.NET Core Middlewares und gRPC Interceptors kann also die Funktionalität von WCF Behaviors in einem gRPC Service nachgebildet werden.

Fazit

Insgesamt bietet der Wechsel von WCF zu gRPC mit ASP.NET Core ein leistungsfähigeres und skalierbareres Framework für die Entwicklung verteilter Anwendungen. Das Code-First-Design mit dem Community-Projekt protobuf-net.Grpc und ASP.NET Core Middlewares sowie die Verwendung von gRPC Interceptors sorgen für einen einfacheren Übergang und verringern den Aufwand für die Portierung vorhandener WCF-Dienste. Die Migration von WCF zu gRPC kann ein komplexes Unterfangen sein, aber sie hat viele Vorteile, einschließlich verbesserter Performance, Skalierbarkeit, Interoperabilität und Zukunftssicherheit.

Wesentlich umfassendere Informationen zu dem Thema gRPC für WCF Entwickler gibt es direkt von Microsoft als eBook.

Mehr Artikel zu ASP.NET Core, gRPC
Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Database Access with Sessions
.NET
KP-round

Data Access in .NET Native AOT with Sessions

.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023
Old computer with native code
.NET
KP-round

Native AOT with ASP.NET Core – Overview

Originally introduced in .NET 7, Native AOT can be used with ASP.NET Core in the upcoming .NET 8 release. In this post, we look at the benefits and drawbacks from a general perspective and perform measurements to quantify the improvements on different platforms.
02.11.2023
Blazor
favicon

gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

Wie in allen anderen browserbasierten Single-Page-Application (SPA) Frameworks, ist Blazor WebAssembly JSON-over-HTTP (über Web- oder REST-APIs) die bei weitem häufigste Methode, um Daten auszutauschen und serverseitige Vorgänge auszulösen. Der Client sendet eine HTTP-Anfrage mit JSON-Daten an eine URL, mitunter über unterschiedliche HTTP-Verben. Anschließend führt der Server eine Operation aus und antwortet mit einem HTTP-Statuscode und den resultierenden JSON-Daten. Warum sollte man das ändern? Nun, es gibt Gründe - vor allem wenn man in einem geschlossenen System ist und .NET sowohl im Frontend als auch im Backend einsetzt.
30.03.2023
Blazor
favicon

Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Stockende UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld - dies sind nur wenige Beispiele alltäglicher Probleme, die der Nutzung von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen, immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.
29.03.2023
Blazor
sg

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.
07.03.2023
.NET
cl-neu

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.
28.02.2023