Blazor WebAssembly + Grpc Web = 未來?

chester·chen 發表於 2022-06-13

Blazor WebAssembly是什麼

首先來說說WebAssembly是什麼,WebAssembly是一個可以使C#,Java,Golang等靜態強型別程式語言,執行在瀏覽器中的標準,瀏覽器廠商基於此標準實現執行引擎。

 

在實現了WebAssembly標準引擎之後,瀏覽器中可以執行由其他語言編譯成的wasm模組。使用強型別程式語言的好處顯而易見:

  • 可以選擇更多的語言,編寫前端邏輯
  • 靜態程式語言編譯成的位元組碼,相對於JS這種指令碼語言執行效率更高
  • 可以使用靜態程式語言生態中的強大類庫

Blazor WebAssembly是dotnet版本的WebAssembly實現,微軟將dotnet執行時編譯成dotnet.wasm模組,我們的程式編譯出來的dll檔案執行在此模組上。

需要注意的是,Blazor WebAssembly是一個完完全全的前端框架,只是邏輯程式碼不再使用JS編寫,而是使用C#編寫。

Grpc Web是什麼

Grpc是一種與語言無關的的高效能遠端過程呼叫(RPC)框架。Grpc有以下優點

  • 現代高效能輕量級 RPC 框架。
  • 協定優先 API 開發,預設使用協議緩衝區,允許與語言無關的實現。
  • 可用於多種語言的工具,以生成強型別伺服器和客戶端。
  • 支援客戶端、伺服器和雙向流式處理呼叫。
  • 使用 Protobuf 二進位制序列化減少對網路的使用。

而Grpc Web是Grpc的前端實現版本,可以使瀏覽器應用直接與Grpc互動。

有了Grpc Web,我們可以直接在Blazor WebAssembly中呼叫Grpc Server,而不用再通過傳統的Http請求方法呼叫。

程式碼演示

GrpcServer

首先需要新建一個Grpc Server

Blazor WebAssembly + Grpc Web = 未來?

 然後為其引入 Grpc.AspNetCore.Web Nuget包,並開啟grpc web

Blazor WebAssembly + Grpc Web = 未來?

app.UseGrpcWeb(); // Must be added between UseRouting and UseEndpoints
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>().EnableGrpcWeb();

之後我們需要為Grpc Server開啟跨域設定,允許跨域訪問

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
    context.Response.Headers.Add("Access-Control-Allow-Headers", "*");
    context.Response.Headers.Add("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH");
    context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
    context.Response.Headers.Add("Access-Control-Max-Age", "100000");
    context.Response.Headers.Add("Access-Control-Expose-Headers", "Grpc-Status,Grpc-Message,Grpc-Encoding,Grpc-Accept-Encoding");
    if (context.Request.Method.ToUpper() == "OPTIONS")
    {
        return;
    }
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

最終Program.cs的程式碼如下

using GrpcService2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();

var app = builder.Build();
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
    context.Response.Headers.Add("Access-Control-Allow-Headers", "*");
    context.Response.Headers.Add("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH");
    context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
    context.Response.Headers.Add("Access-Control-Max-Age", "100000");
    context.Response.Headers.Add("Access-Control-Expose-Headers", "Grpc-Status,Grpc-Message,Grpc-Encoding,Grpc-Accept-Encoding");
    if (context.Request.Method.ToUpper() == "OPTIONS")
    {
        return;
    }
    await next.Invoke();
});

app.UseGrpcWeb();
app.MapGrpcService<GreeterService>().EnableGrpcWeb();

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();

Blazor WebAssembly

現在新建一個WebAssembly專案

Blazor WebAssembly + Grpc Web = 未來?

 為其引入以下nuget包

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.20.1" />
    <PackageReference Include="Grpc.Net.Client" Version="2.46.0" />
    <PackageReference Include="Grpc.Net.Client.Web" Version="2.46.0" />
    <PackageReference Include="Grpc.Tools" Version="2.46.1">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

在其專案檔案中包含進proto檔案

  <ItemGroup>
    <Protobuf Include="..\GrpcService2\Protos\*.proto" GrpcServices="Client" />
  </ItemGroup>

然後將GrpcClient注入容器

builder.Services.AddScoped(p =>
{
    var channel = GrpcChannel.ForAddress("https://localhost:7033/", new GrpcChannelOptions
    {
        HttpHandler = new GrpcWebHandler(new HttpClientHandler())
    });
    var client = new GrpcService2.Greeter.GreeterClient(channel);
    return client;
});

修改Index.razor,讓其訪問grpc server

@page "/"
@inject GrpcService2.Greeter.GreeterClient GreeterClient

<div>grpc web response @Message</div>

@code {
    public string Message { get; set; }
    protected override async Task OnInitializedAsync()
    {
        var reply = await GreeterClient.SayHelloAsync(new GrpcService2.HelloRequest { Name = "test" });
        Message = reply.Message;
    }
}

最終效果如下

Blazor WebAssembly + Grpc Web = 未來?

可以看到整個請求/渲染過程,使用的是C#程式碼編寫的邏輯,沒用到js,原理是因為,blazor webassembly將我們的dotnet執行時,與我們的程式碼編譯後的程式集,執行在了基於webassembly標準實現的瀏覽器引擎中。

Blazor WebAssembly + Grpc Web = 未來?

並且可以看到請求響應體都使用的壓縮過的二進位制形式。效率相對更高

Blazor WebAssembly + Grpc Web = 未來?

 Blazor WebAssembly + Grpc Web = 未來?

webassembly難道是未來?難道未來的某一天要和js say goodbye了嗎?