前回、紹介したgRPCサービスのサンプルコードに、新規の関数を追加してみる。
対象となるサンプルコードは既に紹介している以下のGitHubリンクで公開している。
(注意:2024年1月15日更新→.NET5.0はサポート終了しました。サンプルコードは.NET8.0に更新しました)
既に解説しているが、Sample_GrpcService が gRPCサービスで、他の二つはクライアントモジュールである。
Sample_gRPC_ConsoleApp がコンソールアプリ、Sample_gRPC_WpfApp がWPFクライアントアプリである。
それぞれのクライアントモジュールがgRPCサービスに引数を送信して、結果を受け取り表示する。
今回、追加したメソッドは「単項メソッド」である。
業務システムならほとんど、これだけで済むはずだ。
容量の大きなファイルを送信する場合は「ストリーミング メソッド」が必要になるかも知れないが、今回は見送る。
追加メソッドの仕様
gRPCサービスに追加したメソッドは以下の二つ。
[MyFunction]メソッド
//入力引数
MyRequest {
	string parameter1;
	int32 parameterIntValue;
}
//出力引数
MyFunctionRply {
	string message;
}
//メソッド
MyFunctionRply rply =  MyFunction(MyRequest req);
parameter1文字列の後ろにparameterIntValue数値を文字列にして追加する。
その先頭に「Reply ! 」を追加した文字列を、messageに登録して、返す。
[Calc]メソッド
//入力引数
CalcParameter {
	int32 parameter1;
	int32 parameter2;
}
//出力引数
CalcResult {
	int32 Addition;	//加算
	int32 Subtraction;	//減算
	int32 Multiplication;	//掛算
	int32 Division;		//割算(整数)
}
//メソッド
CalcResult res = Calc (CalcParameter param); 
parameter1とparameter2の二つの数値を引数で渡す。
二つの数値の和を、Additionに登録する。
二つの数値の差を、Subtractionに登録する。
二つの数値の積を、Multiplicationに登録する。
二つの数値の商の整数値を、Divisionに登録する。
CalcResultの値を返す。
Sample_GrpcService の修正
修正するのは「Protosgreet.proto」ファイルと、「ServicesGreeterService.cs」の二つだけ。
greet.proto の修正
まず、入出力引数となる message 宣言を追加する。
[MyFunction 用の引数]
// The request parameters for my function.
message MyRequest {
	string parameter1 = 1;
	int32 parameterIntValue = 2;
}
// The response result for my function.
message MyFunctionRply {
	string message = 1;
}
[Calc 用の引数]
// The request parameters for calc function.
message CalcParameter {
	int32 parameter1 = 1;
	int32 parameter2 = 2;
}
// The response result for calc function.
message CalcResult {
	int32 Addition = 1;
	int32 Subtraction = 2;
	int32 Multiplication = 3;
	int32 Division = 4;
}
番号が message の中で唯一になるように注意する。
次に、メソッド宣言を追加する。
service Greeter 宣言に MyFunction と Calc のメソッド定義を追加する。
SayHello は元から存在したメソッドだ。
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
  // my function
  rpc MyFunction (MyRequest) returns (MyFunctionRply);
  // calc function
  rpc Calc (CalcParameter) returns (CalcResult);
}
これで MyFunction と Calc のシリアライズの仕様が定義できた。
GreeterService.cs の修正
次に class GreeterService の宣言の中に、 MyFunction と Calc のメソッドの実装を記述する。
public override Task<MyFunctionRply> MyFunction(MyRequest request, ServerCallContext context)
{
    return Task.FromResult(new MyFunctionRply
    {
        Message = "Reply ! " + request.Parameter1 + "=" + request.ParameterIntValue
    });
}
public override Task<CalcResult> Calc(CalcParameter parameter, ServerCallContext context)
{
    Int32 value1 = parameter.Parameter1;
    Int32 value2 = parameter.Parameter2;
    return Task.FromResult(new CalcResult
    {
        Addition = value1 + value2,
        Subtraction = value1 - value2,
        Multiplication = value1 * value2,
        Division = value1 / value2
    });
}
単項メソッドの型は決まっているので、
public override Task<MyFunctionRply> MyFunction(MyRequest request, ServerCallContext context)
というシグネチャは「お約束」と考えて良い。
「.proto」の
rpc MyFunction (MyRequest) returns (MyFunctionRply);
で定義した (MyRequest) が、第一引数の「MyRequest request」になり、第二引数は「ServerCallContext context」で固定。
rpc の「returns (MyFunctionRply)」の定義が、返り値の型「Task<MyFunctionRply>」となる。
Taskに成るのは非同期メソッドだからだろう。
これでビルドしてエラーが出なければ完了だ。
次にクライアントモジュールの修正を行う。
そのために、「Protosgreet.proto」ファイルのコピーを使用する。
先に言っておく。
Sample_gRPC_ConsoleApp の修正
まず、コンソールアプリの Sample_gRPC_ConsoleApp から修正してみる。
サービスの「.proto」ファイルをコピーする
gRPCサービスからコピーした「Protosgreet.proto」ファイルを Sample_gRPC_ConsoleApp の「Protosgreet.proto」ファイルへ上書きする。
gRPCサービスの「.proto」と、クライアントの「.proto」は同じでなければならない。
「.proto」の、option csharp_namespace の値をコンソールアプリの名前空間に変更する。この場合は Sample_gRPC_ConsoleApp になる。
サービスメソッドの呼び出し処理を書く
コンソールアプリの「Program.cs」の「static void Main(string[] args)」の処理の後ろに以下の処理を追加する。
クライアント側からサービスメソッドを呼び出す処理だ。
こちらは元のコード。
    class Program
    {
        static void Main(string[] args)
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var client = new Greeter.GreeterClient(channel);
            var response = client.SayHello(new HelloRequest { Name = "Motoi.Tsushima" });
            //var response = client.SayHelloAsync(new HelloRequest { Name = "World" });
            Console.WriteLine("Greeting: " + response.Message);
        }
    }
こちらが末尾に追加するコードになる。
            Console.WriteLine("");
			// my function
            string requestText = "MyRequest";
            Int32 intValue = 23;
            MyRequest myRequest = new MyRequest();
            myRequest.Parameter1 = requestText;
            myRequest.ParameterIntValue = intValue;
            var reply = client.MyFunction(myRequest);
            Console.WriteLine("MyFunction: " + reply.Message);
            Console.WriteLine("");
            // calc function
            Int32 intValue1 = 50;
            Int32 intValue2 = 5;
            var replyCalc = client.Calc(
                new CalcParameter { Parameter1 = intValue1, Parameter2 = intValue2 }
                );
            Console.WriteLine("Calc 結果");
            Console.WriteLine(" 加算=" + replyCalc.Addition);
            Console.WriteLine(" 減算=" + replyCalc.Subtraction);
            Console.WriteLine(" 掛算=" + replyCalc.Multiplication);
            Console.WriteLine(" 割算=" + replyCalc.Division);
後はビルドしてエラーが出なければ修正は完了だ。
コンソールアプリの動作確認
Sample_GrpcService を実行して起動しておく。
その状態で、Sample_gRPC_ConsoleApp を実行して、コンソールに表示される値を確認する。
以下の表示が出れば正常に動作している。
Greeting: Hello Motoi.Tsushima
MyFunction: Reply ! MyRequest=23
Calc 結果
 加算=55
 減算=45
 掛算=250
 割算=10
Sample_gRPC_WpfApp の修正
引き続き、WPFアプリの修正も行う。
サービスの「.proto」ファイルをコピーする
コンソールアプリと同様に、gRPCサービスからコピーした「Protosgreet.proto」ファイルを Sample_gRPC_WpfApp の「Protosgreet.proto」ファイルへ上書きする。
対象となるプロジェクトは「Sample_gRPC_ClassLibrary」になる。
「.proto」の、option csharp_namespace の値をコンソールアプリの名前空間に変更する。この場合は Sample_gRPC_ClassLibrary になる。
画面レイアウトにパラメータの入力項目を追加する
xamlを編集して入力パラメータの値を入力し、出力パラメータを表示する項目を追加する。
<Window x:Class="Sample_gRPC_WpfApp.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:Sample_gRPC_WpfApp"
        mc:Ignorable="d"
        Title="Sample gRPC-WPF" Height="200" Width="620">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20" />
            <RowDefinition Height="20" />
            <RowDefinition Height="30" />
            <RowDefinition Height="20" />
            <RowDefinition Height="30" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0" >
            <TextBlock Text="リクエスト文字列" Width="100" />
            <TextBox x:Name="ReqestTextBox" Width="200" />
            <Button x:Name="ReqestButton" Content="リクエスト" Width="100" Click="ReqestButton_Click"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="1" Height="20" VerticalAlignment="Top">
            <TextBlock Text="リプライ" Width="100" />
            <TextBox x:Name="ReplayTextBox" Width="200" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="2" Height="20" VerticalAlignment="Top" Margin="0,10,0,0">
            <TextBlock Text="MyFunction文字列" Width="100" TextAlignment="Right" />
            <TextBox x:Name="MyFunctionTextBox" Width="200" />
            <TextBlock Text="MyFunction整数" Width="100" TextAlignment="Right" />
            <TextBox x:Name="MyFunctionIntBox" Width="50" TextAlignment="Right"/>
            <Button x:Name="MyFunctionButton" Content="リクエスト" Width="100" Click="MyFunctionButton_Click"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="3" Height="20" VerticalAlignment="Top">
            <TextBlock Text="リプライ" Width="100" />
            <TextBox x:Name="ReplayMyFunctionTextBox" Width="200" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="4" Margin="0,10,0,0">
            <TextBlock Text="整数1" Width="100" TextAlignment="Right"/>
            <TextBox x:Name="Int1TextBox" Width="50" TextAlignment="Right" />
            <TextBlock Text="整数2" Width="100" TextAlignment="Right"/>
            <TextBox x:Name="Int2TextBox" Width="50" TextAlignment="Right" />
            <Button x:Name="CalcButton" Content="計算" Width="100" Margin="30,0,0,0" Click="CalcButton_Click"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="5" Height="20" VerticalAlignment="Top">
            <TextBlock Text="加算" Width="100" TextAlignment="Right" />
            <TextBox x:Name="AdditionTextBox" Width="50" TextAlignment="Right"/>
            <TextBlock Text="減算" Width="100" TextAlignment="Right"/>
            <TextBox x:Name="SubtractionTextBox" Width="50"  TextAlignment="Right" />
            <TextBlock Text="掛算" Width="100"  TextAlignment="Right"/>
            <TextBox x:Name="MultiplicationTextBox" Width="50" TextAlignment="Right" />
            <TextBlock Text="割算" Width="100"  TextAlignment="Right"/>
            <TextBox x:Name="DivisionTextBox" Width="50"  TextAlignment="Right"/>
        </StackPanel>
    </Grid>
</Window>
サービスメソッドの呼び出し処理を書く
クラスライブラリ Sample_gRPC_ClassLibrary の class SampleClient に通信の接続処理を記述する。
ソースファイル MainWindow.xaml.cs の class MainWindow に gRPCサービスメソッドの呼び出し処理を記述する。
クラスを全部掲載する。
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.grpcClient = new SampleClient();
        }
        private SampleClient grpcClient = null;
        private void ReqestButton_Click(object sender, RoutedEventArgs e)
        {
            string requestText = this.ReqestTextBox.Text;
            var reply = this.grpcClient.GreeterClient.SayHello(new HelloRequest { Name = requestText });
            this.ReplayTextBox.Text = reply.Message;
        }
        private void MyFunctionButton_Click(object sender, RoutedEventArgs e)
        {
            string requestText = this.MyFunctionTextBox.Text;
            string requestInt = this.MyFunctionIntBox.Text;
            Int32 intValue;
            Int32.TryParse(requestInt, out intValue);
            MyRequest myRequest = new MyRequest();
            myRequest.Parameter1 = requestText;
            myRequest.ParameterIntValue = intValue;
            var reply = this.grpcClient.GreeterClient.MyFunction(myRequest);
            this.ReplayMyFunctionTextBox.Text = reply.Message;
        }
        private void CalcButton_Click(object sender, RoutedEventArgs e)
        {
            string value1 = this.Int1TextBox.Text;
            string value2 = this.Int2TextBox.Text;
            Int32 intValue1;
            Int32 intValue2;
            Int32.TryParse(value1, out intValue1);
            Int32.TryParse(value2, out intValue2);
            var reply = this.grpcClient.GreeterClient.Calc(
                new CalcParameter { Parameter1 = intValue1, Parameter2 = intValue2 }
                );
            this.AdditionTextBox.Text = reply.Addition.ToString();
            this.SubtractionTextBox.Text = reply.Subtraction.ToString();
            this.MultiplicationTextBox.Text = reply.Multiplication.ToString();
            this.DivisionTextBox.Text = reply.Division.ToString();
        }
    }
前回と違い、SampleClient() のインスタンスを画面に常駐させている。
後はビルドしてエラーが出なければ修正は完了だ。
WPFアプリの動作確認
Sample_GrpcService を実行して起動しておく。
その状態で、Sample_gRPC_WpfApp を実行して、画面に表示されるレイアウトを確認する。
レイアウトに問題が無ければ、入力項目に値を入れて、隣のボタンをクリックしてみる。
MyFunction文字列に文字列「Test」を、MyFunction整数に整数「5」を入力して、「リクエスト」ボタンをクリックする。
リプライに「Reply ! Test=5」と表示されれば正常に動作している。
入力値のチェックはしていないので整数に文字列を入れると誤動作する。
整数1に「50」を、整数2に「5」を入力して、「計算」ボタンをクリックする。
加算に「55」、減算に「45」、掛算に「250」、割算に「10」と表示されれば正常に動作している。
単項メソッドの追加例は以上です
単項メソッドの追加例を紹介しました。
比較的簡単だったと思います。
お役に立てば幸いです。
 
  
  
  
  