🔖

.NET 8 の Blazor で WASM + gRPC のプロジェクトを作る

2024/05/25に公開

この記事は、.NET 8 の Blazor で WASM + gRPC のプロジェクトを作る方法を紹介します。
基本的には、以前書いた.NET 8 の Blazor で WASM + API のプロジェクトを作るの記事と同じですが、gRPC に変更する部分を主に紹介します。

プロジェクトの作成

ここら辺は基本的に前回と同じなのでさくっと説明します。Blazor Web App で WebAssembly を Global で有効になるようにしてプロジェクトを作成します。

そして Routes.razor を以下のようにして静的 SSR の時には Loading... と表示するようにします。

Routes.razor
@if (!OperatingSystem.IsBrowser())
{
    <div>Loading...</div>
}
else
{
    <Router AppAssembly="typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
            <FocusOnNavigate RouteData="routeData" Selector="h1" />
        </Found>
    </Router>
}

gRPC サービスの追加

gRPC ですが .proto ファイルを作成してやるのが標準の方法ですが今回はコードファーストでやっていこうと思います。コードファーストでやる方法は、以下のドキュメントに詳細があるので、そちらもあわせて参考してください。

MyGrpcService というクラスライブラリプロジェクトを作成して以下のライブラリを追加します。

  • protobuf-net.Grpc

そして、サービスのコントラクトを定義していきます。今回はありきたりな挨拶を返すサービスを作成します。

GreetingsService.cs
using ProtoBuf.Grpc;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace MyGrpcService;

[ServiceContract]
public interface IGreetingService
{
    [OperationContract]
    Task<HelloReply> SayHello(HelloRequest request, CallContext context = default);
}

[DataContract]
public record HelloReply(
    [property: DataMember(Order = 1)]
    string Message);

[DataContract]
public record HelloRequest(
    [property: DataMember(Order = 1)]
    string Name);

そして Blazor Web App のサーバー側のプロジェクトに MyGrpcService プロジェクトと以下のライブラリを追加してサービスの実装を行います。

  • protobuf-net.Grpc.AspNetCore
  • Grpc.AspNetCore.Web (gRPC-Web で呼び出すために必要)

そして、サービスの実装を適当に行います。今回はありきたりなハローワールド的なものを以下のように実装しました。

Services/GreetingService.cs
using MyGrpcService;
using ProtoBuf.Grpc;

namespace BlazorWasmAndGrpcSingleProjectSample.Services;

public class GreetingService : IGreetingService
{
    public Task<HelloReply> SayHello(HelloRequest request, CallContext context = default) =>
        Task.FromResult(new HelloReply($"Hello, {request.Name}!"));
}

最後に Program.cs に以下のようにして gRPC エンドポイントとサービスを追加します。さらに、今回は Blazor WebAssembly から呼び出すので gRPC-Web で呼び出せるようにしています。

Program.cs
using BlazorWasmAndGrpcSingleProjectSample.Client.Pages;
using BlazorWasmAndGrpcSingleProjectSample.Components;
using BlazorWasmAndGrpcSingleProjectSample.Services;
using ProtoBuf.Grpc.Server;

var builder = WebApplication.CreateBuilder(args);

// gRPC サービスを追加
builder.Services.AddCodeFirstGrpc();
// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

// gRPC のサービスの追加
app.UseGrpcWeb();
app.MapGrpcService<GreetingService>().EnableGrpcWeb();

app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(BlazorWasmAndGrpcSingleProjectSample.Client._Imports).Assembly);

app.Run();

追加した部分だけのコードを抜粋すると以下の2か所になります。

// gRPC サービスを追加
builder.Services.AddCodeFirstGrpc();
// gRPC のサービスの追加
app.UseGrpcWeb();
app.MapGrpcService<GreetingService>().EnableGrpcWeb();

これで GreetingService が gRPC で呼び出せるようになりました。

gRPC クライアントの追加

では最後に Blazor WebAssembly のプロジェクトから gRPC を呼び出すようにします。まずは、MyGrpcService プロジェクトと以下のパッケージを Blazor WebAssembly のプロジェクトに追加します。

  • Grpc.Net.Client
  • Grpc.Net.Client.Web
  • System.ServiceModel.Primitives

そして、Program.cs に以下のようにして gRPC クライアントを追加します。

Program.cs
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using ProtoBuf.Grpc.Configuration;
using MyGrpcService;
using Grpc.Net.Client.Web;
using Grpc.Net.Client;
using ProtoBuf.Grpc.Client;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

// クライアントを追加
builder.Services.AddSingleton(sp =>
{
    var httpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler());
    return GrpcChannel.ForAddress(
        builder.HostEnvironment.BaseAddress,
        new GrpcChannelOptions
        {
            HttpHandler = httpHandler,
        });
});
builder.Services.AddTransient<IGreetingService>(sp =>
{
    var channel = sp.GetRequiredService<GrpcChannel>();
    return channel.CreateGrpcService<IGreetingService>();
});

await builder.Build().RunAsync();

これで gRPC クライアントが追加されました。あとは、Blazor のコンポーネントから呼び出すだけです。
Home.razor を以下のようにして実際に呼び出してみましょう。

Home.razor
@using MyGrpcService
@page "/"
@inject IGreetingService GreetingService

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<div>
    <label>
        Name:
        <input @bind="Name" />
    </label>
</div>
<div>
      <button @onclick="SayHelloAsync">Say Hello</button>
</div>
<div>
    @Message
</div>

@code {
    private string Name { get; set; } = "";
    private string Message { get; set; } = "";
    private async Task SayHelloAsync()
    {
        var response = await GreetingService.SayHello(new HelloRequest { Name = Name });
        Message = response.Message;
    }
}

実行して、表示された画面のテキストボックスに適当な名前を入力して Say Hello ボタンを押すと、gRPC でサービスが呼び出されて以下のように表示されます。

まとめ

ということで、.NET 8 の Blazor で WASM + gRPC のプロジェクトを作る方法をやってみました。gRPC はコードファーストで実装して .proto を書かない方法で試しました。普通の .proto を使う方法でも、ほぼ同じように作れると思います。

コードは、この記事を書くにあたって作ったプロジェクトを GitHub に置いておきますので、興味があれば参考にしてみてください。

https://github.com/runceel/BlazorWasmAndGrpcSingleProjectSample

Microsoft (有志)

Discussion