在 Blazor WebAssembly 中使用 gRPC-Web


對於單頁面應用程序,gRPC-Web 是 JSON-over-HTTP 的一種方便、高性能的替代方案。

如果你已經了解關於 gRPC 和 gRPC-Web 的一切,你可以跳到 添加 gRPC 服務到一個Blazor WebAssembly 應用程序 一節。如果你只是想要一些簡單的 Blazor WebAssembly + gRPC-Web 應用程序,請看這個倉庫 https://github.com/SteveSandersonMS/BlazorGrpcSamples。

現狀

與其他所有基於瀏覽器的單頁應用程序(SPA)技術一樣,在 Blazor WebAssembly 中,數據交互和觸發服務器端操作的最常見方式是 JSON-over-HTTP。它很簡單:客戶端使用預先約定的 HTTP 方法向某個預先約定的 URL 發出 HTTP 請求,然后服務器執行操作並使用預先約定的 HTTP 狀態碼和預先約定格式的 JSON 數據進行應答。

這種方法通常很有效,這樣做的人往往能過上充實的生活。然而,它也有兩個顯而易見的弱點:

  • JSON 是一種非常冗長的數據格式,它沒有優化帶寬。
  • 沒有任何機制可以保證所有這些預先約定的 url、HTTP 方法、狀態碼 等等的細節在服務器和客戶端之間實際上是一致的。

什么是 gRPC?

gRPC 是一種遠程過程調用(RPC)機制,最初由谷歌開發。對於 SPA,你可以將其視為 JSON-over-HTTP 的替代方案。它直接修復了上面列出的兩個弱點:

  • 它被優化為最小的網絡流量,發送有效的二進制序列化消息
  • 你可以在編譯時保證服務器和客戶端對端點的存在、數據的發送和接收形式達成一致,而不需要指定任何URL、狀態碼 等等。

這是怎么做到的呢?

要編寫gRPC服務,你需要編寫一個.proto文件,它是一組 RPC 服務及其數據形狀的獨立於語言的描述。從這里,你可以用任何語言生成強類型的服務器和客戶端類,從而保證在編譯時符合你的協議。然后在運行時,gRPC 處理(反)序列化數據並以有效的格式(默認為 protobuf )發送/接收消息。

另一個很大的好處是它不是 REST,所以你不必與同事經常爭論哪些 HTTP 方法和狀態代碼在你的場景中是最幸福和幸運的。它只是簡單的 RPC,在我們還是小孩子的時候就真情實感想要的。

為什么不是每個人都在他們的 SPA 中使用 gRPC ?

傳統上,從基於瀏覽器的應用程序中使用 gRPC 是不可能的,因為 gRPC 需要 HTTP/2,而且瀏覽器不公開任何 api,讓 JS、WASM 代碼直接控制 HTTP/2 請求。

但是現在有一個解決方案!gRPC-Web 是 gRPC的擴展,它使 gRPC 與基於瀏覽器的代碼兼容(從技術上講,它是通過 HTTP/1.1 請求執行 gRPC 的一種方式)。gRPC-Web 還沒有流行起來,因為到目前為止還沒有多少服務器或客戶端框架提供對它的支持。

ASP.NET Core 從 3.0 版本開始就提供了強大的 gRPC 支持。現在,在此基礎上,我們將在服務器和客戶端提供對 gRPC-Web的預覽支持。如果你想深入了解細節,可以在來自 James Newton-King 的優秀的 pull request 查看全部實現(https://github.com/grpc/grpc-dotnet/pull/695)。

添加 gRPC 服務到一個 Blazor WebAssembly 應用程序

目前還沒有這方面的項目模板,所以將 gRPC 支持添加到 Blazor WebAssembly 應用程序需要很多步驟,本文是詳細的介紹。但好消息是你只需要做一次這樣的設置。當你完成起步與運行起來后,添加更多的 gRPC 端點並調用它們是非常簡單的。
首先,由於 gRPC-Web 包還沒有發布到 NuGet.org,現在你需要添加兩個臨時的包管理源來獲得 nightly 預覽。
你可以在你的解決方案的根目錄下添加NuGet.config文件。希望一兩個月后就不需要了。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
    <add key="gRPC-nightly" value="https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev" />
    <add key="blazor-nightly" value="https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json" />
  </packageSources>
</configuration>

添加 gRPC 服務到一個托管部署的 Blazor WebAssembly 應用程序

如果你已經在 ASP.NET Core 服務端上托管了一個Blazor WebAssembly 應用程序,默認情況下,你有三個項目:客戶端、服務端和共享項目。我發現定義 gRPC 服務最方便的地方是在共享項目中,因為這樣生成的類對服務器和客戶機都可用。
首先,編輯你的共享項目的.csproj添加必要的 gRPC 包引用:

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.11.2" />
    <PackageReference Include="Grpc.Net.Client" Version="2.27.0-dev202001100801" />
    <PackageReference Include="Grpc.Tools" Version="2.27.0-dev202001081219">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

如果這些包不能正確恢復,請確保添加了 nightly 源。

現在可以創建.proto文件來定義服務了。例如,在共享項目中添加一個名為greet.proto的文件。包含如下內容:

syntax = "proto3";
option csharp_namespace = "GrpcGreeter";
package greet;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

要使gRPC工具從這里生成服務器和客戶端類,請進入共享項目的.csproj並添加以下內容:

  <ItemGroup>
    <Protobuf Include="greet.proto" />
  </ItemGroup>

此時,解決方案應該可以沒有錯誤地編譯通過。

從服務端公開gRPC服務

在你的服務器項目中,創建一個名為GreeterService的新類,包含以下內容:

using Grpc.Core;
using GrpcGreeter;

public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        var reply = new HelloReply { Message = $"Hello from gRPC, {request.Name}!" };
        return Task.FromResult(reply);
    }
}

這個類繼承自 Greeter.GreeterBase,它是從 .proto 文件自動生成的。因此,當你將新斷點添加到 .proto 中時,你將有新方法可以在這里進行重寫來提供具體實現。

服務端的最后一部分是使用新的 api 將其公開為一個 gRPC-Web 服務。你需要為此引用一個包,因此在你的服務端項目的.csproj中,添加以下包引用:

<PackageReference Include="Grpc.AspNetCore" Version="2.27.0-dev202001100801" />
<PackageReference Include="Grpc.AspNetCore.Web" Version="2.27.0-dev202001100801" />

現在,在你的服務器的Startup.cs文件中,修改ConfigureServices以添加以下行:

  services.AddGrpc();

注意:如果你只打算公開gRPC服務,你可能不再需要MVC控制器,在這種情況下,你可以從下面刪除services.AddMvc() endpoints.MapDefaultControllerRoute()

只是在 app.AddRouting(); 的下面添加以下內容,它會處理將傳入的gRPC-web請求映射到服務端,使其看起來像 gRPC請求:

  app.UseGrpcWeb();

最后,在app.UseEndpoints語句塊中注冊你的 gRPC-Web 服務類,並在該語句塊的頂部使用以下代碼行:

  endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb();

就這樣,你的gRPC-Web服務端已經准備好了!

在客戶端中使用gRPC服務

在你的客戶端項目的.csproj中,你需要添加以下兩個 nightly 包的引用:

<PackageReference Include="Grpc.Net.Client.Web" Version="2.27.0-dev202001100801" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="3.2.0-preview1.20052.1" />

后者是為了修復 Blazor WebAssembly 中的一個問題的變通方案,這個問題將在幾周后的下一次預覽中得到修復。如果這些包不能正常恢復,請確保添加了 nightly 源。

現在設置你的客戶端應用程序的依賴項注入系統,以便能夠提供GreeterClient的實例。這將允許你在客戶端應用程序的任何位置調用 gRPC 服務。在客戶端項目的Startup.cs中的ConfigureServices方法中添加以下內容:

services.AddSingleton(services =>
{
    // Create a gRPC-Web channel pointing to the backend server
    var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
    var baseUri = services.GetRequiredService<NavigationManager>().BaseUri;
    var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient });

    // Now we can instantiate gRPC clients for this channel
    return new Greeter.GreeterClient(channel);
});

需要注意的是Greeter.GreeterClient是從.proto file文件中為你生成的代碼。你不必手動實現它!但你還需要添加以下使用語句,使上述代碼編譯通過:

using GrpcGreeter;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using Microsoft.AspNetCore.Components;
using System.Net.Http;

我們快完成了!現在有一個可工作的服務端,並希望還有一個可工作的客戶端。我們只需要從 UI 調用一些 gRPC 服務。例如,在你的Index.razor文件中,替換成如下內容:

@page "/"
@using GrpcGreeter
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="yourName" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    string yourName = "Bert";
    string serverResponse;

    async Task GetGreeting()
    {
        var request = new HelloRequest { Name = yourName };
        var reply = await GreeterClient.SayHelloAsync(request);
        serverResponse = reply.Message;
    }
}

現在在瀏覽器中嘗試一下。用戶界面看起來是這樣的:

如果你查看瀏覽器開發者工具的 network 選項卡中的請求,你會看到它在發送和接收二進制protobuf消息:

總結和示例

現在你已經有了基礎,如果你願意,還可以進一步使用 gRPC 來進行服務器和客戶機之間的所有數據交換。gRPC 工具將為你生成所有的數據傳輸類,提高網絡流量的效率,並消除 url、HTTP 方法、狀態代碼和序列化等 HTTP-over-JSON 的問題。

還有一個更詳細的示例(https://github.com/SteveSandersonMS/BlazorGrpcSamples/tree/master/Hosted),它是一個完整的 Blazor WebAssembly 托管應用程序,使用 gRPC 獲取“天氣預報”數據。如果你對從默認的基於json的解決方案升級到基於gRPC-Web的解決方案所需的具體步驟感興趣,請參閱這個准確地顯示了我所做的更改的差異對比(https://github.com/SteveSandersonMS/BlazorGrpcSamples/commit/72544c54085a35cd89aae20030d7f91d75317a2f)。

添加 gRPC 服務到一個獨立部署的 Blazor WebAssembly 應用程序

如果你正在構建一個純獨立的 Blazor WebAssembly 應用程序,而不是托管在 ASP.NET Core,那么我們就不能對你將擁有什么樣的服務器做任何假設(意思是你只開發客戶端,而服務端是由別人開發的場景)。我們只能假設你要調用一些與 gRPC-Web 兼容的服務端點,它們可能是在其他主機上的 ASP.NET Core 服務上暴露,或者是一個在另一個 gRPC 服務周圍的 Envoy gRPC-Web 包裝器(https://blog.envoyproxy.io/envoy-and-grpc-web-a-fresh-new-alternative-to-rest-6504ce7eb880)。在這里我們唯一關心的是配置你的 Blazor WebAssembly 應用程序來使用它。
設置客戶端應用程序的大多數步驟與上面的“托管部署”情況相同。然而,在某些方面,它有點棘手,因為我們不能依賴於我們在“托管”情況下所做的一些假設。區別如下:

  • 獲取和使用.proto文件

    假定你的外部 gRPC 服務維護者可以為你提供定義該服務的.proto文件。你可以將其復制到你的客戶端項目中,並在.csproj中添加一個引用它的<Proto>項。為了讓工具工作,你還需要添加類似這樣的包引用:

      <!-- Needed temporarily until the next Blazor WebAssembly preview release -->
      <PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="3.2.0-preview1.20052.1" />
    
      <!-- gRPC-Web packages -->
      <PackageReference Include="Google.Protobuf" Version="3.11.2" />
      <PackageReference Include="Grpc.Net.Client" Version="2.27.0-dev202001100801" />
      <PackageReference Include="Grpc.Net.Client.Web" Version="2.27.0-dev202001100801" />
      <PackageReference Include="Grpc.Tools" Version="2.27.0-dev202001081219">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
    
  • 配置客戶端DI服務

    與“托管托管”情況類似,你將向Startup.cs中添加代碼以添加gRPC-Web客戶端服務。這里的主要區別是,你必須知道用於訪問外部服務的 Base URL 是什么,因為我們不能再假設它的 Base URL 跟托管你的客戶端應用程序的服務端的 Base URL 一樣。因此,你的DI服務注冊可能看起來更像以下內容:

    services.AddSingleton(services =>
    {
    #if DEBUG
        var backendUrl = "https://localhost:5001"; // Local debug URL
    #else
        var backendUrl = "https://some.external.url:12345"; // Production URL
    #endif
    
        // Now we can instantiate gRPC clients for this channel
        var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
        var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions { HttpClient = httpClient });
        return new Greeter.GreeterClient(channel);
    });
    

    這將根據你是否在調試模式下構建而改變URL。

總結和示例

就是這樣!現在,你的獨立部署的 Blazor WebAssembly 應用程序可以使用外部的 gRPC-Web 服務。對於完整的可運行示例,下面是一個示例獨立應用程序(https://github.com/SteveSandersonMS/BlazorGrpcSamples/tree/master/Standalone),它在外部 URL 上調用一個 gRPC-Web 服務。 這個示例附帶了一個實際的用於測試的 gRPC-Web 服務器,但是你可以分開考慮它。如果你想確切地看到我更改了什么,這里是與默認項目模板輸出的差異(https://github.com/SteveSandersonMS/BlazorGrpcSamples/commit/d6ec609f2b7e6591958d38e4a207c9b4f52f0feb)。

你的反饋要求

如果你想進一步了解 gRPC,看看 ASP.NET Core gRPC 文檔。請給我們關於你對 gRPC-Web 的意見和經驗的反饋,因為這將幫助我們選擇如何以及是否在未來的 ASP.NET Core 版本中使 gRPC-Web 成 一個標准特性。你可以在這里發布評論,或者在 GitHub 上發布標題中帶有“反饋”的 issue。

翻譯自原文:https://blog.stevensanderson.com/2020/01/15/2020-01-15-grpc-web-in-blazor-webassembly

相關文章:

  • ASP.NET Core 現已支持gRPC-Web
  • .NET Core ❤ gRPC


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM