I was disappointed by the simple example in the MSDN for gRPC with .NET Core, so I thought I'd try out a slightly more involved example.
My main objectives were to understand how the code is generated, and to see how easily I could expand/modify the contracts and what subsequent changes are required to the code.
Turns out that other than an slight issue with Rider's intellisense not picking up the generated files it was surprisingly easy.
I imagined a simple scenario of a Dashboard style website that gathers information from a number of remote/distributed services to display information to the user.
Build the projects
./build.ps1
Run grpcTodo
project and leave it running.
Run grpcWeather
project and leave it running.
Run the grcpDashboard
project and browse to its home page. Page will open automatically if run from Visual Studio.
You should see some information like this:
Welcome Learn about building Web apps with ASP.NET Core.
Temperature 100 C
Priority Date Message 1 26/06/2020 This is a todo 2 26/06/2020 This is another todo
The temperature value comes from grpcWeather
, and the list of todos comes from grpcTodo
.
First we add a package reference to the .NET Core gRPC implementation:
<PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
This includes a number of nested packages. The nested packages are what's usually required for client, and you'll see them later in the client section.
Next we next we need to include and define the contract using the protobuf IDL.
<ItemGroup>
<Protobuf Include="Protos\weather.proto">
<GrpcServices>Server</GrpcServices>
</Protobuf>
</ItemGroup>
protobuf definition:
syntax = "proto3";
option csharp_namespace = "GRpcTest.GRpcWeather";
package grpcTest.grpcWeather;
service Weather {
rpc Temperature(LocationMessage) returns (TemperatureReply);
}
message LocationMessage {
string name = 1;
}
message TemperatureReply {
int32 Celsius = 1;
int32 Fahrenheit = 2;
int32 Kelvin = 3;
}
You can now build the project and the server implementation classes will be generated; by default in the obj
folder.
You can now start implementing the server.
Note: If you are using Rider you'll need to add the following to the csproj for intellisense to work correctly:
<!-- Work-around for Rider intellisense not working -->
<Target Name="Protobuf_Compile_Before_AssemblyReferences" BeforeTargets="ResolveAssemblyReferences">
<CallTarget Targets="_Protobuf_Compile_BeforeCsCompile" />
</Target>
Implementing the server is very easy. Simply inherit from the generated classes. Namespaces and names are as you would expect from the protobuf definition. If you have trouble finding out the class/namespace names then you can just read the code in the generated files in obj
folder.
public class WeatherService : Weather.WeatherBase
{
private readonly ILogger<WeatherService> _logger;
public WeatherService(ILogger<WeatherService> logger)
{
_logger = logger;
}
public override Task<TemperatureReply> Temperature(LocationMessage request, ServerCallContext context)
{
return Task.FromResult(
new TemperatureReply { Celsius = 100, Fahrenheit = 212, Kelvin = 373 });
}
}
Note that the implemention is simply an override and therefore provides everything you need to know what your inputs and outputs are.
The client is in many ways much simpler.
First add the required packages for the client:
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.12.3" />
<PackageReference Include="Grpc" Version="2.30.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.27.0" />
<PackageReference Include="Grpc.Tools" Version="2.30.0" />
</ItemGroup>
Then add references to the service contracts. Note that GrpcServices
is 'Client' this time.
<ItemGroup>
<Protobuf Include="..\grpcWeather\Protos\weather.proto">
<GrpcServices>Client</GrpcServices>
</Protobuf>
<Protobuf Include="..\grpcTodo\Protos\todo.proto">
<GrpcServices>Client</GrpcServices>
</Protobuf>
</ItemGroup>
Building the code now will generate the required clients.
All that's needed then is create the clients and invoke the methods:
var weatherChannel = GrpcChannel.ForAddress("https://localhost:5002");
var weather = new Weather.WeatherClient(weatherChannel);
var temp = weather.Temperature(new LocationMessage { Name = "London" });
TemperatureCelcius = temp.Celsius;
var todoChannel = GrpcChannel.ForAddress("https://localhost:5003");
var todoClient = new Todo.TodoClient(todoChannel);
var todos = todoClient.GetTodos(new GetTodosMessage());
Todos = todos.Todos;
- grpcTodo has an example of a method without parameters; note how it needs an empty message object as its input.
- grpcTodo has an example of an enumerable, or list, construct.
- grpcDashboard is almost entirely boilerplate, most things of interest are the
grpcDashboard.csproj
and theindex.cshtml.cs
file.