{"id":32369710,"url":"https://github.com/eduardsergeev/greeterservice","last_synced_at":"2026-04-11T08:01:12.351Z","repository":{"id":101423815,"uuid":"604700711","full_name":"EduardSergeev/GreeterService","owner":"EduardSergeev","description":"Example of gRPC/Protobuf code generation","archived":false,"fork":false,"pushed_at":"2024-07-08T09:45:59.000Z","size":12897,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-07-09T09:39:29.687Z","etag":null,"topics":["code-first","code-generation","dotnet","grpc","protobuf"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/IndependentReserve.Grpc.Tools#readme-body-tab","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EduardSergeev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-21T15:59:52.000Z","updated_at":"2024-07-09T09:39:29.688Z","dependencies_parsed_at":null,"dependency_job_id":"960c92c5-dfb1-4bfb-9bfa-ccb0f7f2f7af","html_url":"https://github.com/EduardSergeev/GreeterService","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/EduardSergeev/GreeterService","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EduardSergeev%2FGreeterService","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EduardSergeev%2FGreeterService/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EduardSergeev%2FGreeterService/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EduardSergeev%2FGreeterService/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EduardSergeev","download_url":"https://codeload.github.com/EduardSergeev/GreeterService/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EduardSergeev%2FGreeterService/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31673067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T17:19:37.612Z","status":"online","status_checked_at":"2026-04-11T02:00:05.776Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-first","code-generation","dotnet","grpc","protobuf"],"created_at":"2025-10-24T20:01:21.583Z","updated_at":"2026-04-11T08:01:12.339Z","avatar_url":"https://github.com/EduardSergeev.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gRPC/Protobuf code generation example\n\n[MSDN gRPC example](https://learn.microsoft.com/en-us/aspnet/core/grpc/) implemented using [IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools/#readme-body-tab) package\n\n[![Build Status](https://github.com/EduardSergeev/GreeterService/workflows/build/badge.svg)](https://github.com/EduardSergeev/GreeterService/actions?query=workflow%3Abuild+branch%3Amaster)\n[![Linux benchmarks](https://eduardsergeev.github.io/GreeterService/bench-linux.svg)](https://eduardsergeev.github.io/GreeterService/ubuntu-latest/results/SingleDto-report.html)\n[![Windows benchmarks](https://eduardsergeev.github.io/GreeterService/bench-windows.svg)](https://eduardsergeev.github.io/GreeterService/windows-latest/results/SingleDto-report.html)\n\n## The purpose of the package\n\n[IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools) adds code-first way to implement gRPC services using [Grpc.Tools](https://www.nuget.org/packages/Grpc.Tools).  \n\n`Grpc.Tools` requires service and message contracts to be defined in [Protobuf](https://en.wikipedia.org/wiki/Protocol_Buffers) to generate C# message classes and service stubs. However since Protobuf is not native to .NET this requirement increases the complexity of the code and often requires ad-hoc solutions for data conversion between generated gRPC/Protobuf code and the rest of the system code.  \n\n`IndependentReserve.Grpc.Tools` on the other hand generates all Protobuf definition required by `Grpc.Tools` from a plain .NET ([POCO](https://en.wikipedia.org/wiki/Plain_old_CLR_object)) interface and POCO [DTO](https://en.wikipedia.org/wiki/Data_transfer_object)'s referenced by the interface methods. It also generates gRPC service and client classes which internally use generated by `Grpc.Tools` service and client code but operate with the original DTO (gRPC-agnostic) classes.\n\n## Example structure\n\n### Client \u0026 service code\n\nThis example uses [IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools) (the _tool_) to generate gRPC code from a plain .NET interface (the _source interface_): simple [IGreeterService](./Greeter.Common/IGreeterService.cs) which is equivalent to [MSDN example](https://learn.microsoft.com/en-us/aspnet/core/grpc/) and more involved [IGreeterExtendedService.cs](./Greeter.Common/IGreeterExtendedService.cs) which instead of `string` type parameters uses a [set of DTO](./Greeter.Common/Dto) classes.  \n\nHere is how `IGreeterExtendedService` source interface is defined:\n\n```c#\npublic interface IGreeterExtendedService\n{\n    Greeting SayGreeting(Person person);    \n}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eReferenced DTO definitions:\u003c/summary\u003e\n\n  ```c#\n  public readonly record struct Person\n  (\n      Name Name,\n      List\u003cName\u003e OtherNames,\n      string[] Aliases,\n      Details Details\n  );\n\n  public readonly record struct Name\n  (\n      Title Title,\n      string FirstName,\n      string LastName,\n      string? MiddleName = null\n  );\n  \n  public enum Title\n  {\n      Mr, Mrs, Miss, Ms, Sir, Dr\n  }\n  \n  public readonly record struct Details\n  (\n      DateTime DateOfBirth,\n      double Height,\n      decimal Length,\n      Address[] Addresses\n  );\n  \n  public readonly record struct Address\n  (\n      string[] Street,\n      string City,\n      string? State,\n      uint? Postcode,\n      string? Country\n  );\n\n  public readonly record struct Greeting\n  (\n      string Subject,\n      IEnumerable\u003cstring\u003e Lines\n  );  \n  ```\n\n\u003c/details\u003e\n\ngRPC/Protobuf code (both service and client) is generated by the tool during the build in _target project_ [Greeter.Grpc](./Greeter.Grpc). This project contains only [Greeter.Grpc.csproj file](./Greeter.Grpc/Greeter.Grpc.csproj) which:\n\n- [Contains](./Greeter.Grpc/Greeter.Grpc.csproj#L9) `PackageReference` to the tool's NuGet package:\n\n  ```xml\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"IndependentReserve.Grpc.Tools\" Version=\"4.1.*\" /\u003e\n  \u003c/ItemGroup\u003e\n  ```\n\n- [Marks](./Greeter.Grpc/Greeter.Grpc.csproj#L12) dependent source project with `GenerateGrpc` attribute:\n\n  ```xml\n  \u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\Greeter.Common\\Greeter.Common.csproj\" GenerateGrpc=\"true\" /\u003e\n  \u003c/ItemGroup\u003e  \n  ```\n\n  which forces the tool to generate gRPC code for all source interfaces found `Greeter.Common` project\n\nGenerated gRPC code is automatically included into the build pipeline. Generated code contains a set of `*.proto` and `*.cs` files but in practice developer only needs to know about two C# classes:\n\n- `GreeterExtendedServiceGrpcService`: gRPC service class which derives from generated by `Grpc.Tools` service stub class `GreeterExtendedServiceBase` and can be directly hosted in ASP.NET app\n  \n  \u003cdetails\u003e\n    \u003csummary\u003eGenerated service class content:\u003c/summary\u003e\n\n  ```c#\n  public partial class GreeterExtendedServiceGrpcService : Greeter.Common.Grpc.GreeterExtendedService.GreeterExtendedServiceBase\n  {\n      private readonly ILogger\u003cGreeterExtendedServiceGrpcService\u003e _logger;\n      private readonly IGreeterExtendedService _greeterExtendedService;\n\n      public GreeterExtendedServiceGrpcService(\n          ILogger\u003cGreeterExtendedServiceGrpcService\u003e logger,\n          IGreeterExtendedService greeterExtendedService)\n      {\n          _logger = logger;\n          _greeterExtendedService = greeterExtendedService;\n      }\n\n      public override async Task\u003cSayGreetingResponse\u003e SayGreeting(SayGreetingRequest request, ServerCallContext context)\n      {\n          var args = MapperTo\u003cValueTuple\u003cGreeter.Common.Person\u003e\u003e.MapFrom(new { Item1 = request.Person });\n          var result = _greeterExtendedService.SayGreeting(@person: args.Item1);\n          return MapperTo\u003cSayGreetingResponse\u003e.MapFrom(new { Result = result });\n      }\n  }\n  ```\n\n  \u003c/details\u003e\n\n- `GreeterExtendedServiceGrpcClient`: gRPC client class which implements `IGreeterExtendedService` by calling service via gRPC using generated by `Grpc.Tools` `GreeterExtendedServiceClient` client class\n\n  \u003cdetails\u003e\n    \u003csummary\u003eGenerated client class content:\u003c/summary\u003e\n\n  ```c#\n  public partial class GreeterExtendedServiceGrpcClient : GrpcClient, IGreeterExtendedService\n  {\n      private readonly Lazy\u003cGreeter.Common.Grpc.GreeterExtendedService.GreeterExtendedServiceClient\u003e _client;\n\n      public GreeterExtendedServiceGrpcClient(IGrpcServiceConfiguration config, bool useGrpcWeb = true)\n          : base(config, useGrpcWeb)\n      {\n          var invoker = Channel.CreateCallInvoker();\n          SetupCallInvoker(ref invoker);\n          _client = new(() =\u003e new(invoker));\n      }\n\n      partial void SetupCallInvoker(ref CallInvoker invoker);\n\n      private Greeter.Common.Grpc.GreeterExtendedService.GreeterExtendedServiceClient Client =\u003e _client.Value;\n\n      public Greeter.Common.Greeting SayGreeting(Greeter.Common.Person @person)\n      {\n          var response = Client.SayGreeting(MapperTo\u003cSayGreetingRequest\u003e.MapFrom(new { Person = @person }));\n          return MapperTo\u003cWrapper\u003cGreeter.Common.Greeting\u003e\u003e.MapFrom(response).Result;\n      }\n\n      public async System.Threading.Tasks.Task\u003cGreeter.Common.Greeting\u003e SayGreetingAsync(Greeter.Common.Person @person)\n      {\n          var response = await Client.SayGreetingAsync(MapperTo\u003cSayGreetingRequest\u003e.MapFrom(new { Person = @person })).ConfigureAwait(false);\n          return MapperTo\u003cWrapper\u003cGreeter.Common.Greeting\u003e\u003e.MapFrom(response).Result;\n      }\n  }\n  ```\n\n  \u003c/details\u003e\n\nBoth classes are placed in `obj/{Configuration}/{TargetFramework}/Grpc/Partials` directory.  \n\n[Service code](./Greeter.Service) then uses `GreeterExtendedServiceGrpcService` class to [map gRPC service](./Greeter.Service/Program.cs#L22) thus exposing service implementation via gRPC:\n\n```c#\napp.MapGrpcService\u003cGreeterExtendedServiceGrpcService\u003e();\n```\n\nwhile [client code](./Greeter.Client) can [instantiate and execute](./Greeter.Client/Program.cs#L77-L80) `GreeterExtendedServiceGrpcClient` methods to call the service via gRPC:\n\n```c#\nvar extendedClient = new Greeter.Common.Grpc.GreeterExtendedServiceGrpcClient(config, false);\n\nWriteGreeting(extendedClient.SayGreeting(person));\nWriteGreeting(await extendedClient.SayGreetingAsync(person));\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eWriteGreeting definition:\u003c/summary\u003e\n\n  ```c#\n  void WriteGreeting(Greeting greeting)\n  {\n      WriteLine(greeting.Subject);\n      foreach(var line in greeting.Lines)\n      {\n          WriteLine(line);\n      }\n  }\n  ```\n\n\u003c/details\u003e\n\n### DTO ↔ Protobuf conversion test code\n\nThe tool can also automatically generate unit tests which test `DTO → Protobuf → byte[] → Protobuf → DTO` (round-trip) conversion/serialization path.  \n[Greeter.Test](./Greeter.Test) project contains the example of configuration for this scenario. Entire configuration is located in [Greeter.Test.csproj](./Greeter.Test/Greeter.Test.csproj) file:\n\n- Just like for gRPC code generation the `PackageReference` is [added](./Greeter.Test/Greeter.Test.csproj#L9) to the project:\n\n  ```xml\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"IndependentReserve.Grpc.Tools\" Version=\"4.1.*\" /\u003e\n  \u003c/ItemGroup\u003e\n  ```\n\n- But instead of `GenerateGrpc` attribute the source project is [marked](./Greeter.Test/Greeter.Test.csproj#L16) by `GenerateGrpcTests` attribute which forces the tool to generate tests for all source interface methods:\n\n  ```xml\n  \u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\Greeter.Common\\Greeter.Common.csproj\" GenerateGrpcTests=\"true\" /\u003e\n    \u003cProjectReference Include=\"..\\Greeter.Grpc\\Greeter.Grpc.csproj\" /\u003e\n  \u003c/ItemGroup\u003e\n  ```\n\n  Note that here we also [reference](./Greeter.Test/Greeter.Test.csproj#L17) `Greeter.Grpc` project which contains generated gRPC/Protobuf code to be tested by generated test code\n\n## How to build and run the examples\n\nServer:\n\n```console\ncd Greeter.Service\ndotnet run\n```\n\nClient:\n\n```console\ncd Greeter.Client\ndotnet run\n```\n\nTests:\n\n```console\ncd Greeter.Test\ndotnet test\n```\n\nDocker:\n\n```console\ndocker build -t greeter-service -f Greeter.Service/Dockerfile .\ndocker run -it --rm -p 5001:443 greeter-service\n```\n\nBenchmarks:\n\n```console\ncd Greeter.Bench\ndotnet run -c Release\n```\n\n## Benchmark results\n\nLatest benchmark results can be found on [docs](../docs/docs) branch:\n- [Linux](../docs/docs/ubuntu-latest/results)\n- [Windows](../docs/docs/windows-latest/results)\n\n\u003cdetails\u003e\n  \u003csummary\u003eBenchmark results example:\u003c/summary\u003e\n\n[Serialisation](Greeter.Bench/StringArraySerialisation.cs) of `string[]` vs `string?[]` collection (vs JSON serialisation as baseline):\n\n\u003ca href=\"https://eduardsergeev.github.io/GreeterService/ubuntu-latest/results/StringArraySerialisation-report.html\"\u003e\n    \u003cimg src=\"https://eduardsergeev.github.io/GreeterService/ubuntu-latest/results/StringArraySerialisation-barplot.png\" height=\"500\"/\u003e\n\u003c/a\u003e\n\n\u003c/details\u003e\n\n## Quick start with the package\n\n[IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools) package can generate all required gRPC code from a plain .NET interface (so called _source interface_). The only requirement is that source interface must be located in a separate assembly/project which the project where gRPC code is generated (_target project_) depends on.  \nTo add gRPC code into target project do:\n\n1. Add a package references to [IndependentReserve.Grpc](https://www.nuget.org/packages/IndependentReserve.Grpc) and to [IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools), e.g. via:\n \n   ```console\n   dotnet add package IndependentReserve.Grpc\n   dotnet add package IndependentReserve.Grpc.Tools\n   ```\n \n   \u003cdetails\u003e\n     \u003csummary\u003eWhy do we need two packages:\u003c/summary\u003e\n \n     Actually if you just manually add `PackageReference` to `IndependentReserve.Grpc.Tools` like that:\n \n     ```xml\n     \u003cItemGroup\u003e\n       \u003cPackageReference Include=\"IndependentReserve.Grpc.Tools\" Version=\"4.1.215\" /\u003e\n     \u003c/ItemGroup\u003e\n     ```\n \n     the reference to `IndependentReserve.Grpc` is added implicitly (transitively) so it does not have to be added explicitly.  \n     However due to a bug in the [latest IndependentReserve.Grpc.Tools](https://www.nuget.org/packages/IndependentReserve.Grpc.Tools/4.1.215) when the package reference to it is added via `dotnet add` command a set of `\u003c*Assets/\u003e` attributes are also added:\n \n     ```xml\n     \u003cItemGroup\u003e\n       \u003cPackageReference Include=\"IndependentReserve.Grpc.Tools\" Version=\"4.1.215\"\u003e\n         \u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n         \u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n       \u003c/PackageReference\u003e\n     \u003c/ItemGroup\u003e\n     ```\n \n     These unnecessary `\u003c*Assets/\u003e` attributes break transitive dependency on `IndependentReserve.Grpc` which later result in compilation errors due to missing dependent types from `IndependentReserve.Grpc`.  \n \n   \u003c/details\u003e\n\n1. In target project `*.csproj` file mark `ProjectReference` to dependent project which contains source interface(s) with `GenerateGrpc` attribute, e.g.:\n\n   ```xml\n   \u003cItemGroup\u003e\n     \u003cProjectReference Include=\"..\\Greeter.Common\\Greeter.Common.csproj\" GenerateGrpc=\"true\" /\u003e\n   \u003c/ItemGroup\u003e    \n   ```\n\n   \u003cdetails\u003e\n     \u003csummary\u003eHow source interfaces are located:\u003c/summary\u003e\n \n   By default the tool searches for all public interfaces which names match `Service$` regular expression (e.g. `ISomeService`) and generates all required gRPC-related code for every found interface.  \n   To use a different pattern for interface search specify a custom regular expression (.NET flavor) via `GrpcServicePattern` attribute, e.g.:\n \n   ```xml\n   \u003cItemGroup\u003e\n     \u003cProjectReference Include=\"..\\Service.Interface.csproj\" \u003e\n       \u003cGenerateGrpc\u003etrue\u003c/GenerateGrpc\u003e\n       \u003cGrpcServicePattern\u003eI[^.]*ServiceInterface$\u003c/GrpcServicePattern\u003e\n     \u003c/ProjectReference\u003e\n   \u003c/ItemGroup\u003e\n   ```\n \n   \u003c/details\u003e\n\nOnce this is done all relevant gRPC code is generated and added to target project build pipeline. Both server and client code is generated, specifically, the following two classes are expected to be used by client or service code:\n\n- `{service-name}GrpcService.cs`: generated gRPC service implementation\n  \u003cdetails\u003e\n     \u003csummary\u003eWhat is in gRPC service class:\u003c/summary\u003e\n\n  [Grpc.Tools](https://www.nuget.org/packages/Grpc.Tools)-based gRPC service implementation which depends on source interface (required parameter in constructor) which is expected to implement underlying service logic. Effectively this implementation simply exposes passed source interface implementation via gRPC interface.\n\n  \u003c/details\u003e\n\n- `{service-name}GrpcClient.cs`: generated gRPC client implementation  \n  \u003cdetails\u003e\n     \u003csummary\u003eWhat is in gRPC client class:\u003c/summary\u003e\n\n  This class implements source interface by calling the service via gRPC (using internal gRPC client class in turn generated by [Grpc.Tools](https://www.nuget.org/packages/Grpc.Tools)). For each method from source interface both synchronous and asynchronous methods are generated.\n\n  \u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feduardsergeev%2Fgreeterservice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feduardsergeev%2Fgreeterservice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feduardsergeev%2Fgreeterservice/lists"}