.NET Core になると WCF のサーバーサイドが消えて移行先として gRPC があげられてるのを何処かで見た気がします。OSS の WCF もあった気がするけど、そっちはよく見てない。
ということで、ASP.NET Core 3.0 Preview で gRPC 試してみようと思います。
プロジェクトの作成
今日は出先のカフェでコーヒー飲みながら Surface Go で書いてます。なので Visual Studio 2019 は入ってない(Surface Go には重すぎた)ので、Visual Studio Code でいきます。
適当なフォルダーで空の Web アプリを作ります。
$ dotnet new web -o GrpcServer
ソリューションも作って追加しておきましょう。
$ dotnet new sln $ dotnet sln add GrpcServer/GrpcServer.csproj
Visual Studio Code でソリューションのあるフォルダーを開いて task.json とかを生成して Ctrl + Shift + B
でビルドしたり F5
でデバッグできるようにしました。便利。
gRPC のためのパッケージを追加します。
$ dotnet add .\GrpcServer\GrpcServer.csproj package Grpc.AspNetCore -v 0.1.22-pre3
そして、.proto
ファイルを生成しましょう。これも dotnet new
で生成できます。後で作成するクライアントでも使う予定なので、ソリューションのあるフォルダーに Proto/Proto.proto
とかみたいな感じで作りました。
$ dotnet new proto -o Proto
サービスの定義を追加します。
syntax = "proto3"; option csharp_namespace = "GrpcSample"; service Greeter { rpc Greet (GreetRequest) returns (GreetReply); } message GreetRequest { string name = 1; } message GreetReply { string message = 1; }
サーバープロジェクトに追加するために .csproj
を編集しましょう。
<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>netcoreapp3.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Grpc.AspNetCore"Version="0.1.22-pre3" /></ItemGroup><ItemGroup><!-- これを追加 --><Protobuf Include="../Proto/Proto.proto"LinkBase="Proto/Proto.proto"GrpcServices="Server" /></ItemGroup></Project>
これでビルドをすると GrpcServer/obj/Debug
に Proto.cs
と ProtoGrpc.cs
が生成されます。これを継承してサービスを実装します。
GrpcServer
プロジェクトに Services
フォルダーを作って、そこに GreeterService.cs
を作って以下のように Greeter.GreeterBase
を継承する形で処理を作ります。
using System.Threading.Tasks; using Grpc.Core; using GrpcSample; namespace GrpcService.Services { publicclass GreeterService : Greeter.GreeterBase { publicoverride Task<GreetReply> Greet(GreetRequest request, ServerCallContext context) { return Task.FromResult(new GreetReply { Message = $"Hello {request.Name}", }); } } }
.proto
に定義したクラスとサービスのメソッドのひな型は基本クラスで定義されているので、やることはメソッドをオーバーライドして実装するだけです。簡単。
Startup.cs
で gRPC 機能の有効化と上で作成したサービスを登録する処理を追加します。
using GrpcService.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace GrpcServer { publicclass Startup { publicvoid ConfigureServices(IServiceCollection services) { services.AddGrpc(); // これと } publicvoid Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreeterService>(); // これを追加 endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } } }
これでサーバーは完成しましたといいたいところですが、もうちょっとだけ設定を… gRPC は HTTP/2 を使うので、その設定を追加します。appsettings.json
に以下のような設定を追加します。
{"Logging": {"LogLevel": {"Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" }}, "AllowedHosts": "*", "Kestrel": {"EndpointDefaults": {"Protocols": "Http2" }}}
Kestrel の部分が追加したものになります。
クライアントの作成
サーバーだけ作っても、誰も呼んでくれないと何もできないのでクライアントも作ります。WPF でいきましょう。さくっとプロジェクトを作ってソリューションに追加します。
$ dotnet new wpf -o GrpcClient $ dotnet sln add .\GrpcClient\GrpcClient.csproj
そして gRPC のクライアント側に必要なパッケージを入れます。
dotnet add .\GrpcClient\GrpcClient.csproj package Google.Protobuf -v 3.9.1 dotnet add .\GrpcClient\GrpcClient.csproj package Grpc.Net.Client -v 0.1.22-pre3 dotnet add .\GrpcClient\GrpcClient.csproj package Grpc.Tools -v 2.23.0
そして、GrpcClient.csproj
に Protobuf タグを追加します。今回は生成してもらうのはクライアントなので GrpcServices 属性には Client を設定してます。余談ですがクライアントとサーバーの両方を生成してほしいときは Both とかくみたいです。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"><PropertyGroup><OutputType>WinExe</OutputType><TargetFramework>netcoreapp3.0</TargetFramework><UseWPF>true</UseWPF></PropertyGroup><ItemGroup><PackageReference Include="Google.Protobuf"Version="3.9.1" /><PackageReference Include="Grpc.Net.Client"Version="0.1.22-pre3" /><PackageReference Include="Grpc.Tools"Version="2.23.0"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets><PrivateAssets>all</PrivateAssets></PackageReference></ItemGroup><ItemGroup><Protobuf Include="../Proto/Proto.proto"LinkBase="Proto/Proto.proto"GrpcServices="Client" /></ItemGroup></Project>
そして、名前を入力するための TextBox とサービスを呼ぶための Button を置いた画面を MainWindow.xaml
に定義して…
<Window x:Class="GrpcClient.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:GrpcClient"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><StackPanel><TextBox x:Name="textBoxName" /><Button Content="Call gRPC service"Click="CallGrpcServiceButton_Click" /></StackPanel></Window>
コードビハインドにサービスを呼び出すコードを書きましょう。
using System; using System.Net.Http; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using GrpcSample; namespace GrpcClient { /// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclass MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void CallGrpcServiceButton_Click(object sender, RoutedEventArgs e) { using (var client = new HttpClient { BaseAddress = new Uri("https://localhost:5001") }) { var greetServices = Grpc.Net.Client.GrpcClient.Create<Greeter.GreeterClient>(client); var response = await greetServices.GreetAsync(new GreetRequest { Name = textBoxName.Text, }); MessageBox.Show(response.Message); } } } }
そうするとコンパイルエラー!!
MainWindow.xaml.cs(16,7): error CS0246: The type or namespace name 'GrpcSample' could not be found (are you missing a using directive or an assembly reference?) [c:\Users\k_ota\source\repos\GrpcLab\GrpcClient\GrpcClient_teh220ey_wpftmp.csproj]
起きてるエラーとしては以下の Issue と似てるけど、こっちはクラシックツールチェーン…
試しに WPF じゃなくてコンソールアプリで同じ手順を踏んで呼び出す処理を書いたらコンパイルエラーにならないので WPF on .NET Core 用のツールまわりのバグかな?とりあえずの回避方法はクライアントコードの生成をクラスライブラリにうつすことです。
プロジェクトを作成して、必要な参照を追加したりします。
$ dotnet new classlib -o GrpcClientLib $ dotnet sln add .\GrpcClientLib\GrpcClientLib.csproj
生成されるのは .NET Standard 2.0 のプロジェクトなのですが、Grpc.Net.Client 0.1.22-pre3 は .NET Standard 2.1 (ターゲットにしてるライブラリ始めてみた!)なので GrpcClientLib.csproj の netstandard2.0 を netstandard2.1 に書き換えてから下記コマンドでライブラリを追加します。
$ dotnet add .\GrpcClientLib\GrpcClientLib.csproj package Google.Protobuf -v 3.9.1 $ dotnet add .\GrpcClientLib\GrpcClientLib.csproj package Grpc.Net.Client -v 0.1.22-pre3 $ dotnet add .\GrpcClientLib\GrpcClientLib.csproj package Grpc.Tools -v 2.23.0
そして GrpcClientLib.csproj
に、Protobuf タグの定義を追加します。
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Google.Protobuf"Version="3.9.1" /><PackageReference Include="Grpc.Tools"Version="2.23.0"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets><PrivateAssets>all</PrivateAssets></PackageReference></ItemGroup><ItemGroup><Protobuf Include="../Proto/Proto.proto"LinkBase="Proto/Proto.proto"GrpcServices="Client" /></ItemGroup></Project>
WPF 側からは Protobuf のタグを削除しておきましょう。
そして、GrpcClient に GrpcCLientLib への参照を追加します。
$ dotnet add .\GrpcClient\GrpcClient.csproj reference .\GrpcClientLib\GrpcClientLib.csproj
これでコンパイルエラーなしでビルドが通るようになります。
まだ一度も入れたことがない人は .NET Core の開発用の証明書をインストールして
$ dotnet dev-certs https --trust
dotnet run
でサーバーとクライアントを起動して試してみましょう。
まずは、サーバー
$ dotnet run --project .\GrpcServer\GrpcServer.csproj
そして、クライアント
$ dotnet run --project .\GrpcClient\GrpcClient.csproj
適当に TextBox に何か入れてボタンを押すと無事動きました
Azure にデプロイ!!
Azure の App Service に gRPC のサービスをデプロイして動かすことはできないみたいです。残念。
AKS 使えば出来そうですが、ここに書くにはちょっとヘビーなので、また今度トライしてみて書きます。
まとめ
ASP.NET Core の gRPC 割とサクッと作れていい感じです。 LTS 版の .NET Core 3.1 が出たら使ってみたいなぁ。でも App Service の対応は早くしてほしいところ。
ソースコードは、GitHub にあげておきました。