gRPC server within a WPF desktop application?
Asked Answered
T

2

7

I'm currently playing around with gRPC and have so far created a simple C# hello world demo, using a Visual Studio ASP.Net Core gRPC Service project as the server, and a client console app.

What I'd like to do next is have the gRPC server running within a .Net 6 WPF desktop app (call it "AppA"), allowing another desktop app ("AppB") to make requests for information from AppA. Is this even possible?

Normally I would use WCF for this, but I was warned off CoreWCF due to its immaturity.

Terryterrye answered 21/10, 2022 at 9:54 Comment(3)
The managed server of gRPC is based on ASP.NET, so really this is: "can a process be both an ASP.NET server, and a WPF host"; I don't know the answer to that, but it is an intriguing question! However, I would have questions here - in particular, around why WPF is the server and not the client in this scenario. Would it perhaps make more sense to have a separate server part, that both AppA and AppB talk to as clients? Note that the unmanaged gRPC server is probably a lot easier to hook here, but the unmanaged gRPC core is marked end-of-life nowBandylegged
@MarcGravell the idea of making the gRPC server a "middle-man" did cross my mind, but this would of course be a more complex solution than if the server was part of the WPF app where it could reference the types needed to handle client requests. I assume gRPC does support this middle-man approach, with the server somehow being able to forward requests (and their responses) back and forth between two clients?Terryterrye
that isn't an inbuilt scenario, as in the general case you'd need to think about what that means in terms of N clients; however, it is certainly one that you can orchestrate pretty easily once you've figured out what those rules are; you'd presumably set up a duplex pipe on each, and use something like a Channel<T> or similar as an intermediaryBandylegged
T
9

I found a way to host a gRPC service from within a WPF desktop app.

The project needs to reference the "Grpc.AspNetCore" package (no "proto" stuff, you'll see why later).

Then, there's the code to start the web application. For the purposes of my demo app I implemented this in MainWindow.xaml.cs:

private WebApplication? _app;

public MainWindow()
{
    InitializeComponent();
    Task.Run(() => StartServer());
}

private void StartServer()
{
    var builder = WebApplication.CreateBuilder();

    // Despite adding a "launchSettings.json" file to the project, I couldn't find a way 
    // for the builder to pick it up, so had to configure the URLs here:
    builder.WebHost.UseUrls("http://*:5219", "https://*:7219");

    builder.Services.AddGrpc();

    _app = builder.Build();
    _app.MapGrpcService<TestService>();
    _app.Run();
}

private void MainWindow_OnClosing(object? sender, CancelEventArgs e)
{
    _app?.StopAsync();
}

The call to _app.Run() blocks (until the web application stops), hence why I call StartServer() on a b/g thread.

I found an article stating that the proto compiler doesn't work properly in a WPF project. Although it was generating the classes, build errors indicated that these could not be found. The solution was to add the "protos" stuff and service classes to a class library instead, then reference this from the WPF "server". The class library project needed to reference the following packages:

  • Google.Protobuf
  • Grpc.AspNetCore
  • Grpc.Tools
Terryterrye answered 21/10, 2022 at 14:37 Comment(1)
I've just managed to do this on dot net 8. The latest version. Proto compiler works fine - no need for a separate class library. But remember to set the build action for the .proto file, as Protobuf compiler (file properties). It'll automatically generate the classes etc. Visual Studio 2022. Also for some reason, port 7219 specified in the example is usable, the first one isn't. Maybe I'm missing something but I thought both should workOke
U
1

Our WPF .NET 6 app is a gRPC server using Grpc.Core NUGET packages. I have not migrated to the new Grpc.NET implementation. when I do, hopefully it doesn't mandate Asp.NET rather than WPF. Below are the details. Hope it gets you where you need to go!

After creating your server definition, here is the method in our WPF app that creates the server:

        private void CreateServer()
    {
        string[] ipAddresses = GetLocalIPAddresses();

        // insecure
        m_server = new Server
        {
            Services =
            {
                GrpcServer.BindService(this).Intercept(new IpAddressAuthenticator())
            }
        };

        // Add the IP addresses
        for (int i = 0; i < ipAddresses.Length; i++)
        {
            m_server.Ports.Add(new ServerPort(ipAddresses[i], remPort, ServerCredentials.Insecure));
        }

    }

Here is the IpAddressAuthenticator code:

public class IpAddressAuthenticator : Interceptor
{
    private readonly HashSet<string> m_authenticatedIps = new HashSet<string>()
    {
        "127.0.0.1",
        "::1"
    };

    private void VerifyPeer(ServerCallContext context)
    {
        context.Status = new Status(StatusCode.OK, $"Authenticated peer: {context.Peer}");
    }

    private bool TryTakeIpAddress(string peer, out string ipAddress)
    {
        // ex.
        // "ipv4:127.0.0.1:12345"
        // "ipv6:[::1]:12345"

        var ipv4Match = Regex.Match(peer, @"^ipv4:(.+):");
        if (ipv4Match.Success)
        {
            ipAddress = ipv4Match.Groups[1].Value;
            return true;
        }

        var ipv6Match = Regex.Match(peer, @"^ipv6:\[(.+)\]");
        if (ipv6Match.Success)
        {
            ipAddress = ipv6Match.Groups[1].Value;
            return true;
        }

        ipAddress = "";
        return false;
    }

    public override Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, ServerCallContext context, ClientStreamingServerMethod<TRequest, TResponse> continuation)
    {
        VerifyPeer(context);
        return base.ClientStreamingServerHandler(requestStream, context, continuation);
    }

    public override Task DuplexStreamingServerHandler<TRequest, TResponse>(IAsyncStreamReader<TRequest> requestStream, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, DuplexStreamingServerMethod<TRequest, TResponse> continuation)
    {
        VerifyPeer(context);
        return base.DuplexStreamingServerHandler(requestStream, responseStream, context, continuation);
    }

    public override Task ServerStreamingServerHandler<TRequest, TResponse>(TRequest request, IServerStreamWriter<TResponse> responseStream, ServerCallContext context, ServerStreamingServerMethod<TRequest, TResponse> continuation)
    {
        VerifyPeer(context);
        return base.ServerStreamingServerHandler(request, responseStream, context, continuation);
    }

    public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
    {
        VerifyPeer(context);
        return base.UnaryServerHandler(request, context, continuation);
    }
}
Unamerican answered 8/4 at 19:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.