概要
C#でAPIを立てる際、最近はMagicOnionばっかり使ってます。今までBlazorServerやWinFormsとMagicOnionサーバを通信させることが多かったんですが、最近はBlazorWebAssemblyを使用することが多くなってきました。その際に、WebAssemblyだと普通のgRPCが使えないため、MagicOnionをgRPC-Webに対応させる必要がありました。ネット上にその方法がなかなか落ちていなかったので、備忘録的な感じで残しておきます。
なお、この記事ではMagicOnionの環境がすでに整っている前提で手順を書きます。そもそもMagicOnionの使い方がわからないという方はこちらの時期を参照ください。
ちなみに今回説明しているコードは以下のリポジトリにありますので、参考にしていただければと思います。
https://github.com/Uta-member/SampleProject.MO.Web
環境
- VisualStudio 2022 Community
- .NET8
- C# 13
- MagicOnion 7.0.2
サーバ側の設定
サーバのサービスクラス側は特に変える必要がなく、パッケージを追加してProgram.cs
をちょっと変えれば対応できます。
パッケージの追加
サーバ側のプロジェクトに以下のパッケージをインストールします。
Program.csの修正
以下のように修正を行います。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddMagicOnion();
// ここから追加 >>>
// CorePolicyを追加
// 内容はプロジェクトによって合わせてください
builder.Services
.AddCors(
o => o.AddPolicy(
"AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));
// ここまで <<<
var app = builder.Build();
// ここから追加 >>>
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
app.UseCors();
// ここまで <<<
// ミドルウェアの追加
// app.MapMagicOnionService();
app.MapMagicOnionService()
// gRPC-webを有効化
.EnableGrpcWeb()
// 上で設定したCORS Policy名を指定する
.RequireCors("AllowAll");
app.MapGet(
"/",
() => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();
基本的にはCORSの設定を行いgRPC-webを有効化するだけです。
クライアント側の設定
クライアント側もソース自体というよりはGrpcChannelのインスタンスの設定のみで対応可能です。
パッケージの追加
クライアント側のプロジェクトに以下のパッケージをインストールします。
- Grpc.Net.Client.Web
GrpcChannelの修正
今回はとりあえず最低限の動きを再現するためにコンソールアプリで実装します。
以下のようにProgram.csを記述します。
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using MagicOnion.Client;
using SampleProject.MO.Web.Interface;
// gRPC-Webのクライアントを作成
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
// サーバのIPを入れる
// 実際のアプリではDIコンテナに入れて共有などを行うと思う
// オプションにgRPC-WebのHTTPクライアントを指定
GrpcChannel channel = GrpcChannel.ForAddress(
"https://localhost:7283",
new GrpcChannelOptions { HttpClient = httpClient });
// チャンネルを使ってサービスのクライアントを作成する
IMOUserService userService = MagicOnionClient.Create<IMOUserService>(channel);
// 実行
var userResult = userService.FindUserByUserId("userId");
var user = userResult.GetAwaiter().GetResult();
// 普通にawaitも使えます
// var user = await userService.FindUserByUserId("userId");
if(user != null)
{
Console.WriteLine($"UserId: {user.Id}");
Console.WriteLine($"UserName: {user.Name}");
} else
{
Console.WriteLine("ユーザが見つかりませんでした");
}
Console.ReadLine();
実行
実行するとちゃんとサーバ側の処理を呼べているのがわかりますね。
実際にはコンソールではなくBlazorWebAssemblyなどで使うことになると思います。
また、これはC#に限らずReactなどのような別の言語からも呼べるはずなので、そのあたりも気力があれば記事にしていきたいと思います。
さいごに
ネット上になかなかこの方法が落ちておらず、gRPC-Webに関してもそこまで詳しくなかったので、ちゃんとBlazorWASMで通信できるようになるまでかなり時間がかかりました。。この記事で救われる人がいれば幸いです。
ちなみにSwaggerとの共存も可能です。基本的にはgRPC-WebのミドルウェアのあとにSwaggerのミドルウェアをかまして、最後にMagicOnionのミドルウェアをかませればOKですが、そのあたりの細かい解説はMagicOnionをSwaggerでテストする方法の記事にまとめたいと思います。