在 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 servervar 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 channelreturn 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 DEBUGvar backendUrl = "https://localhost:5001"; // Local debug URL #elsevar backendUrl = "https://some.external.url:12345"; // Production URL #endif// Now we can instantiate gRPC clients for this channelvar 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
在 .NET Core 上 Code-first 方式實現 gRPC?
近期,觀測分析平臺 SkyWalking 的 .NET 自動探針 (SkyAPM-dotnet) 也已經支持了 grpc-dotnet 遠程調用的鏈路跟蹤采集,歡迎大家使用!如果喜歡,也請大家給點個星星!
項目地址:https://github.com/SkyAPM/SkyAPM-dotnet
總結
以上是生活随笔為你收集整理的在 Blazor WebAssembly 中使用 gRPC-Web的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【实战 Ids4】║ 在Swagger中
- 下一篇: 【在路上2】快递的运单轨迹