As part of migrating a fairly large (for our team size) code base into modern technologies, moving away from WCF as our internal communications protocol was essential, as WCF is not supported in modern .NET (.NET Core, if you like).

We went with gRPC, for a couple of reasons:

  • Performance
  • Statically typed, generated contracts with native tooling fits internal communication well.
  • Compared to REST, no more pointless discussions about whether we should use PUT or POST, or what resource you're operating on. The operation on the contract says exactly what will happen.
  • For the future: Great tooling and support across various languages.

...and so far, it has been working out great. When all the initial setup is done, adding endpoints and distributing updated contracts ready to use works like a charm.

How we do it

Our code structure essentially looks like this:

  • MyService.sln
    • MyService.Host - the host project, which outputs an executable app when built.
      • /Protos/ - Original protos. This is where we add operations and message definitions.
    • MyService.ProtobufContracts - Distributed as a nuget package with pre-built clients.

The ProtobufContracts-project file only contains some build steps to copy the protobuf-files (*.proto) from the Host-project into itself, before the Grpc.Tools-tooling makes sure that clients are built in the background. This is what the whole project file looks like:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Version>1.5.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Grpc.Core" Version="2.35.0" />
    <PackageReference Include="Google.Protobuf" Version="3.14.0" />
    <PackageReference Include="Grpc.Tools" Version="2.35.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <Protobuf Include="Protos/*.proto" GrpcServices="Client" ProtoRoot="Protos" />
  </ItemGroup>

  <Target Name="CopyProtosBeforeBuild" BeforeTargets="BeforeCompile" DependsOnTargets="PrepareForBuild">
    <CreateItem Include="..\MyService.Host\Protos\**\*.*">
      <Output TaskParameter="Include" ItemName="ProtoContracts" />
    </CreateItem>
    <Copy SourceFiles="@(ProtoContracts)" DestinationFolder="Protos" />
  </Target>
</Project>

Note: We're using the Grpc.Core NuGet here, since we're not on .NET 5/modern enough ASP.NET Core yet. Getting away from WCF is part of that journey. I highly recommend starting with the Grpc.AspNetCore package instead if you can.

When this contract project is built, it also builds gRPC-clients ready for you to call in your code, just like shown in examples like this.

To distribute this now pre built client, dotnet pack it with suitable parameters for your setup, and install it in your consuming code.

When it comes to migrating from WCF to gRPC, if you are fine with a 1:1-conversion, there are even tools that can automate this process for you.

We haven't tried yet, but in theory, you should be able to reuse the same concept to package a gRPC client for this service in the same way for all other supported languages which has some sort of package system.