.NET Core 3.0 + gRPC (Part 1)

Let’s explore using .NET Core 3.0 and gRPC.

At the time of writing .NET Core 3.0 Preview 8 is the latest version, and is officially supported by Microsoft – it’s even used to power the .NET Site.

Too long, didn’t read? I don’t like long, rambling articles either.

What we’re building – a simple chat server that echos your reply

Get Started

Get started by creating an empty ASP.NET Core project template.

(Heads up! I switched to using Rider pretty early after starting this, as Visual Studio for Mac had a ton of issues with intellisense and finding the right files. I didn’t have the same issue in the Windows version, though.)

Add Dependencies

Add the gRPC and Protobuf dependencies via NuGet. We’ll also need to add a nuget.config file since this tutorial uses the nightly build.

Note: I’ll do my best to update this tutorial once the packages are officially published, until then you may see some discrepancies while the core and gRPC team finalize things.

Nuget Config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
        <add key="dotnet-windowsdesktop" value="https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json" />
        <add key="aspnet-aspnetcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json" />
        <add key="aspnet-aspnetcore-tooling" value="https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json" />
        <add key="aspnet-entityframeworkcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json" />
        <add key="aspnet-extensions" value="https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json" />
        <add key="gRPC repository" value="https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev" />    
    </packageSources>
</configuration>

Server Nuget Packages

<PackageReference Include="Google.Protobuf" Version="3.9.1" />
<PackageReference Include="Grpc.AspNetCore" Version="0.2.23-dev201908160701" />

Add Proto File

Since we’re using Protobuf we’ll need a .proto file. This guide doesn’t cover extensive details on Protobuf; browse Protobuf documentation.

Add another top level folder to contain your protobuf specifications, and then add the chat.proto file.

syntax = "proto3";

option csharp_namespace = "ChatServer";

package Chat;

service Chat {
  rpc SendMessage (ChatMessageRequest) returns (ChatMessageReply);
}

message ChatMessageRequest {
  string from = 1;
  string to = 2;
  string message = 3;
}

message ChatMessageReply {
  string from = 1;
  string to = 2;
  string message = 3;
}

Your directory structure should now look like this:

Protos/chat.proto
ChatServer/Program.cs
ChatServer/Startup.cs
ChatServer/ChatServer.csproj
ChatServer/appsettings.json

You’ll now want to add the following snippet to your ChatServer.csproj to link your .proto files:

<ItemGroup>
  <Protobuf Include="..\Protos\chat.proto" GrpcServices="Server" Link="Protos\chat.proto" />
</ItemGroup>

At this point, you should be able to build your solution. It’s time to add the service. Add the following file ChatServer/Services/ChatService.cs:

public class ChatService : Chatter.ChatterBase
{
    public override Task<ChatMessageReply> SendMessage(ChatMessageRequest request, ServerCallContext context)
    {
        // just echo the response back to the client for now
        return Task.FromResult(new ChatMessageReply
        {
            From = request.From,
            To = request.To,
            Message = request.Message
        });
    }
}

At this point, you’ll want to register the gRPC service with .NET Core. You’ll do that by adding it to the Configure section of your Startup class. Depending on how you generated the project, you might have varying levels of things added. Ensure you have gRPC added to the service collection and the endpoints mapped:

public void ConfigureServices(IServiceCollection services)
{
    // change 1: add gRPC to the service collection
    services.AddGrpc();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // change 2: map the chat service
        endpoints.MapGrpcService<ChatService>();
        
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Build your solution and run. You should successfully be able to build, and you should also be able to still see the hello world response when launching the root URL. Next we’ll create a client to consume our service.

Using a gRPC Client

We’ve done most of the hard work creating the protos and getting the server set up. Let’s test this by adding a gRPC client to consume the service. Create a new project called ChatServer.Console. I used the dotnet CLI for this since Rider is having issues detecting the preview version of .NET Core: dotnet new console -n ChatServer.Console

Once you’ve created the project, link the Proto files like we did in the Server project but this time choose Client as the value for GrpcServices: GrpcServices="Client".

Client Nuget Packages

<PackageReference Include="Google.Protobuf" Version="3.9.1" />
<PackageReference Include="Grpc.Net.Client" Version="0.2.23-dev201908160701" />
<PackageReference Include="Grpc.Tools" Version="2.24.0-dev201908181015" PrivateAssets="All" />

Build your project to generate the client files needed and then add the following to your Program class in the console application, replacing the URL with yours if it is different.

namespace ChatServer.Console
{
    class Program
    {
        static async Task<int> Main(string[] args)
        {
            var client = CreateClient();
            var response = await client.SendMessageAsync(new ChatMessageRequest
            {
                From = "Cody Mullins",
                To = "Lionel Richie",
                Message = "Hello, is it me you're looking for?"
            });
            
            System.Console.WriteLine($"{response.Message}");
            
            return 1;
        }

        private static Chatter.ChatterClient CreateClient()
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var client = new Chatter.ChatterClient(channel);
            return client;
        }
    }
}

Special Note for Mac Users

If you’re primarily developing on a Mac, this bit is for you. Otherwise Windows (and probably Linux) users – read on to the next section. Unfortunately, TLS isn’t supported for Mac right now. This is a bummer, but not a deal-breaker unless your servers are also Macs; does anyone actually do this?

You’ll need to ensure you’ve added a non-TLS endpoint and set the switch in your client. Update your Server appsettings.json to have an Http endpoint:

"Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http2"
    },
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5000"
      }
    }
  }

Add the following snippet to the top of your Console application, before you’ve created any instances of HttpClient (or the Grpc client). Note: do not run this in production or all your traffic will be unencrypted. I’ve wrapped this in an IFDEF, but there might be a more elegant way to do it.

#if DEBUG
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
#endif

Run The Code

Alright, fire up your Server and then run your console app. You should see the traffic in both debug windows. You’re done! Your first gRPC service + client in .NET Core 3. We’ll take this example and build on it for part two, so stay tuned!

Stumbling Blocks & Issues

  • Visual Studio for Mac wasn’t playing well with the preview version of Core as well as the preview version of the gRPC tools. I ended up using JetBrains Rider to get the file generation working. This worked in Visual Studio for Windows, though.
  • Mac users can’t run with TLS – see the above note.
  • The gRPC tools for the client wouldn’t generate – at least in Rider – while both the Server and the Client were in the same solution. Once I opened them in separate solutions, the problem was solved.
  • Adding the Server packages for gRPC tooling automatically set some flags in the ChatServer.csproj – such as marking PrivateAssets="All" (we wanted this). This wasn’t automatic in the client package and had to be added manually.

References

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s