Создадим свой сервис grpc. Для этого создадим либо в Visual Studio новый проект по шаблону ASP.NET Core Empty
, либо с помощью .NET CLI новый проект
по шаблону web:
Допустим, проект будет называться GrpcServiceApp.
После создания проекта для работы с gRPC добавим в проект Nuget-пакет Grpc.AspNetCore.
Для файлов Protobuf создадим в проекте новую папку Protos. Далее в эту папку добавим новый файл, который назовем metanit.proto. Если мы работаем в Visual Studio, то мы можем воспользоваться встроенным шаблоном Protocol Buffer File:
В ином случае можно просто добавить текстовый файл и переименовать его.
В добавленном файле metanit.proto определим следующий код:
syntax = "proto3"; option csharp_namespace = "GrpcServiceApp"; package metanit; message Request{ string word = 1; } message Response{ string word = 1; string translation = 2; } service Translator { // определение метода Translate, // который получает сообщение Request // и отправляет сообщение Response rpc Translate (Request) returns (Response); }
Здесь определено сообщение Request
, которое имеет одно поле - word. Предположим, что клиент будет посылать через это поле слово на перевод.
Также определено сообщение Response
, которое представляет ответ сервера клиенту. Оно имеет два поля. Через поле word посылаем запрошенное слово, а через поле
translation - его перевод.
Также определен сервис Translator, который имеет один метод Translate
. Этот метод принимает сообщение Request и отправляет в ответ сообщение Response.
Далее отредактируем csproj-файл проекта. После добавления Nuget-пакета он выглядит следующим образом:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" /> </ItemGroup> </Project>
Добавим в него ссылку на файл metanit.proto, изменив следующим образом:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <Protobuf Include="Protos\metanit.proto" GrpcServices="Server" /> </ItemGroup> <ItemGroup> <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" /> </ItemGroup> </Project>
Сохраним файл. После этого на основании кода из файла metanit.proto должны автоматически сгенерироваться все необходимые классы C#. Они будут располагаться в проекте
в папке obj\Debug\net8.0\Protos
Далее добавим в проект папку Services, а в нее новый класс, который назовем TranslatorService:
Определим в файле TranslatorService.cs следующий код:
using Grpc.Core; namespace GrpcServiceApp.Services { public class TranslatorService : Translator.TranslatorBase { Dictionary<string, string> words = new () { {"red", "красный"}, {"green", "зеленый" }, {"blue", "синий" } }; public override Task<Response> Translate(Request request, ServerCallContext context) { // получаем отправленное слово var word = request.Word; Console.WriteLine($"Запрошено слово: {word}"); // ищем в словаре и получаем его в переменную translation if(!words.TryGetValue(word, out var translation)) { // если слово не найдено translation = "не найдено"; } // отправляем ответ return Task.FromResult(new Response { Word = word, Translation = translation }); } } }
Функционал пакета Grpc.Tools по определению файла metanit.proto автоматически генерирует статический класс, который по умолчанию называется по имени сервиса из файла proto (то есть в данном случае
Translator). И этот класс содержит собственно абстрактный класс сервиса C#, который называется по шаблону [имя_сервиса]Base
(в данном случае
TranslatorBase). То есть чтобы определить в коде C# свою реализацию сервиса, нам надо унаследовать класс от Translator.TranslatorBase.
И здесь класс TranslatorService как раз представляет конкретную реализацию сервиса Translator из файла proto и наследуется от автогенерируемого абстрактного класса Translator.TranslatorBase.
В классе TranslatorService в качестве условной базы данных определяем словарь words, в котором ключи - английские слова, а значения - их русскоязычный перевод.
Для обмена сообщениями с клиентом переопределяем метод Translate, который в качестве первого параметра принимает сообщение от клиента в виде объекта Request. Это объект автосгенерированного класса Request в соответствии с файлом metanit.proto.
Второй параметр - объект ServerCallContext передает контекст вызова сервиса. В данном случае он нам пока не нужен.
В самом методе Translate получаем отправленное клиентом слово:
var word = request.Word;
Пытаемся найти для него в словаре перевод, а в случае остутствия перевода устанавливаем заглушку. И в конце возвращаем объект автосгенерированного класса Response - сообщение, которое получит клиент:
return Task.FromResult(new Response { Word = word, Translation = translation });
Поскольку метод возвращает не просто Response, а Task<Response>
, то возвращаем объект через метод Task.FromResult()
Но по умолчанию сервис TranslatorService никак не участвует в обработке запросов. Для этого изменим код в файле Program.cs следующим образом:
using GrpcServiceApp.Services; // пространство имен сервиса TranslatorService var builder = WebApplication.CreateBuilder(args); // добавляем сервисы для работы с gRPC builder.Services.AddGrpc(); var app = builder.Build(); // встраиваем TranslatorService в обработку запроса app.MapGrpcService<TranslatorService>(); app.MapGet("/", () =>"Hello World!"); app.Run();
Вначале добавляется поддержка сервисов gRPC:
builder.Services.AddGrpc();
Затем встраиваем сервис TranslatorService в систему маршрутизации для обработки запроса:
app.MapGrpcService<TranslatorService>();
И в конце запустим приложение:
Для теста gRPC-сервиса создадим проект консольного приложения, которое пусть будет называться GprcClientApp. И после создания прежде всего добавим в него следующие Nuget-пакеты:
Grpc.Net.Client
Google.Protobuf
Grpc.Tools
Далее создадим в проекте консольного приложения новую папку Protos и в нее скопируем из проекта сервиса файл metanit.proto
option csharp_namespace = "GrpcServiceApp";
на строку
option csharp_namespace = "GrpcClientApp";
Далее нам надо отредактировать файл проекта с расширением csproj, который называется по имени проекта. После добавления пакетов он выглядит примерно следующим образом:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Google.Protobuf" Version="3.25.1" /> <PackageReference Include="Grpc.Net.Client" Version="2.59.0" /> <PackageReference Include="Grpc.Tools" Version="2.59.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> </ItemGroup> </Project>
В узел <Project>
добавим следующий элемент:
<ItemGroup> <Protobuf Include="Protos\metanit.proto" GrpcServices="Client" /> </ItemGroup>
То есть после изменения файл проекта будет выглядеть следующим образом:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <Protobuf Include="Protos\metanit.proto" GrpcServices="Client" /> </ItemGroup> <ItemGroup> <PackageReference Include="Google.Protobuf" Version="3.25.1" /> <PackageReference Include="Grpc.Net.Client" Version="2.59.0" /> <PackageReference Include="Grpc.Tools" Version="2.59.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> </ItemGroup> </Project>
Далее в проекте консольного клиента изменим код файла Program.cs следующим образом:
using Grpc.Net.Client; using GrpcClientApp; // список слов для получения перевода var words = new List<string>() { "red", "yellow", "green" }; // создаем канал для обмена сообщениями с сервером // параметр - адрес сервера gRPC using var channel = GrpcChannel.ForAddress("https://localhost:7229"); // создаем клиент var client = new Translator.TranslatorClient(channel); // отправляем каждое слово сервису для получения перевода foreach(var word in words) { // формируем сообщение для отправки Request request = new Request { Word= word }; // отправляем сообщение и получаем ответ Response response = await client.TranslateAsync(request); // выводим слово и его перевод Console.WriteLine($"{response.Word} : {response.Translation}"); }
Для взаимодействия с сервисом нужен соответствующий класс клиента. И в проекте клиента опять же с помощью пакета Grpc.Tools будут автоматически генерироваться
нужные класса. В частности, будет генерироваться статический класс, который называется по имени сервиса из файла proto (в данном случае Translator). А в этом статическом
классе будет создаваться класс клиента, который по умолчанию назвается [название_сервиса_proto]Client
(в данном случае TranslatorClient). Соответственно для
обращения к клиенту нам необходимо использовать класс Translator.TranslatorClient
.
В конструктор этого класса передается канал для взаимодействия с сервисом
using var channel = GrpcChannel.ForAddress("https://localhost:7229"); var client = new Translator.TranslatorClient(channel);
Для отправки сервису сообщений у клиента определен метод Translate/TranslateAsync, который в качестве параметра принимает объект автосгенерированного класса Request, а возвращает - объект автогенерируемого класса Response. И в данном случае в цикле пробегаемся по списку слов, для каждого из них создаем сообщение Request и посылаем на сервер методом TranslateAsync. А в качестве результата получаем объект Response с переводом слова.
Request request = new Request { Word= word }; Response response = await client.TranslateAsync(request);
Запустим проект консольного клиента: