對於單頁面應用程序,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