gRPC je fenomén dnešní doby. Moderní a výkonově efektivní protokol se rychle rozšiřuje a my si dnes ukážeme, jak ho použít pro komunikaci mez Blazor WebAssembly front-endem a ASP.NET Core backendem (hostem):
- Efektivně využijeme možnosti sdílení kódu mezi serverovou a klientskou částí. Použijeme uspořádání code-first a „contract“ (interface pro volanou službu a definice datových objektů) dáme do assembly sdílené serverovou i klientskou částí solution.
- Pro překonání omezení browserů použijeme gRPC-Web rozšíření.
Celou implementaci si ukážeme na jednoduché příkladu – použijeme výchozí šablonu Blazor WebAssembly App z Visual Studia (ASP.NET Core hosted, verze šablony .NET7) a připravenou ukázku Fetch data, která v této šabloně používá REST API, předěláme na gRPC-Web volání s použitím code-first.
Pojďme na to, bude to jen pár kroků:
1. MyBlazorSolution.Server – Příprava ASP.NET Core host
Nejprve si na straně serveru připravíme infrastrukturu pro gRPC. Půjdeme rovnou do varianty s rozšířením gRPC-Web s code-first podporou a nainstalujeme NuGet balíčky
Zaregistrujeme podpůrné služby do dependency-injection ve Startup.cs
:
builder.Services.AddCodeFirstGrpc(config => { config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal; });
Přidáme gRPC middleware někam mezi UseRouting()
a definici end-pointů (před MapXy()
metody):
app.UseGrpcWeb(new GrpcWebOptions() { DefaultEnabled = true });
2. MyBlazorSolution.Shared – Definice contractu služeb (code-first)
Nyní v podobě interface
definujeme, jak bude naše služba vypadat. Interface následně využijeme jak na straně serveru (vytvoříme jeho implementaci), tak na straně klienta (necháme si vygenerovat gRPC-klienta, který bude interface implementovat a my ho v našem kódu budeme přímo využívat prostřednictvím dependency injection).
Přidejte do projektu NuGet balíček, který nám umožní interface dekorovat potřebnými atributy
Najděte si ukázkový soubor WeatherForecast.cs
z šablony projekty. Obsahuje definici návratové datové zprávy, kterou nám nyní vrací ukázkové REST API. Tuto třídu předěláme do následující podoby:
[DataContract]
public class WeatherForecast
{
[DataMember(Order = 1)]
public DateTime Date { get; set; }
[DataMember(Order = 2)]
public int TemperatureC { get; set; }
[DataMember(Order = 3)]
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
- Přidali jsme atribut
DataContract
pro označení třídy, kterou budeme používat jako datovou zprávu gRPC. - Přidali jsme atributy
DataMember
, kterými jsme označili prvky, které se mají přes gRPC přenášet (ostatní se ignorují, zdeTemperatureF
je počítané a dopočítá se nám z ostatních údajů na klientovi kdykoliv znovu). Každému prvku je potřeba nastavitOrder
, kterým se definuje pevný layout pro používanou protobuf serializaci. - Původní typ
DateOnly
jsme vyměnili zaDateTime
. Musíme se držet typů podporovaných použitou protobuf serializací.
Dále potřebujeme vytvořit interface, který bude celou službu popisovat:
[ServiceContract]
public interface IWeatherForecastFacade
{
Task<List<WeatherForecast>> GetForecastAsync(CancellationToken cancellationToken = default);
}
- Atribut
[ServiceContract]
nám označuje použitelnost pro gRPC (lze později využít i pro automatické registrace). - Z podstaty síťové komunikace by měl být celý interface asynchronní.
- Můžeme použít volitelný
CancellationToken
, který nám může zprostředkovat signál o předčasném ukončení komunikace klientem (nebo přerušení spojení).
3. MyBlazorSolution.Server – Implementace a publikování služby
Nyní zbývá připravený interface implementovat na straně serveru (použijeme lehce modifikovaný kód z ukázkového WeatherForecastController
, který můžete nyní smazat):
public class WeatherForecastFacade : IWeatherForecastFacade
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<List<WeatherForecast>> GetForecastAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Today.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToList());
}
}
Připravenou službu vypublikujeme skrze Startup.cs
:
app.MapGrpcService<WeatherForecastFacade>();
MyBlazorSolution.Client – konzumace gRPC služby
Nyní už zbývá jen službu použít v Blazor WebAssembly front-endu. Celou definici máme k dispozici v podobě IWeatherForecastFacade
interface s jeho WeatherForecast
datovou třídou.
Do projektu přidáme potřebné NuGet balíčky:
V Program.cs
zaregistrujeme gRPC-Web infrastrukturu a klienta (ve factory podobě):
builder.Services.AddTransient<GrpcWebHandler>(provider => new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
builder.Services.AddCodeFirstGrpcClient<IWeatherForecastFacade>((provider, options) =>
{
var navigationManager = provider.GetRequiredService<NavigationManager>();
var backendUrl = navigationManager.BaseUri;
options.Address = new Uri(backendUrl);
})
.ConfigurePrimaryHttpMessageHandler<GrpcWebHandler>();
No a nyní již můžeme IWeatherForecastFacade
ve front-endovém projektu kdekoliv použít tak, že si necháme službu nainjectovat pomocí dependency injection. Například tedy upravíme FetchData.razor
, aby používalo naší novou gRPC službu namísto původního REST API:
@inject IWeatherForecastFacade WeatherForecastFacade
...
@code {
private List<WeatherForecast>? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await WeatherForecastFacade.GetForecastAsync();
}
}
Hotovo. Projekt by měl být nyní spustitelný a stránka Fetch data bude nyní komunikovat prostřednictvím gRPC-Web.
Svoje řešení si můžete ověřit vůči vzorové repository
Další rozšíření a pokročilejší techniky
gRPC služba může samozřejmě přijímat i vstup. V takovém případě použijte jeden vstupní parametr pro příchozí zprávu – datovou třídu vytvořenou stejně jako jsme připravili výstupní WeatherForecast
. Obvykle se tyto třídy označují jako Data Transfer Object a dostávají tak suffix Dto
. Implementace je obvykle jako C# record
.
Pokud máme v našem projektu authentizaci a authorizaci, pak můžeme na implementující třídě/metodě použít atribut [Authorize]
, stejně jako bychom ho použili na controlleru/action.
Na publikovaný gRPC endpoint můžeme aplikovat libovolné techniky jako na jakýkoliv jiný namapovaný serverový endpoint (rate limiting, caching, …).
gRPC má v sobě podporu interceptorů jejichž prostřednictvím můžeme gRPC komunikaci dále vylepšovat
- předávat si ze serveru na klienta výjimky (základní podpora je vestavěna, ale může se vám hodit ji obohatit o specifický handling vlastních scénářů),
- předávat si z klienta na server požadovanou culture (do jakého jazyka je front-end přepnut),
V pokročilejší variantě uspořádání můžete zajistit i automatickou registraci interface a datových contractů, aniž byste je museli dekorovat atributy [ServiceContract]
, [DataContract]
a [DataMember(Order = ...)]
. Vše toto a mnohé další najdete připravené v:
- šabloně nového projektu https://github.com/havit/NewProjectTemplate-Blazor
- knihovnách Havit.Blazor
Obojí open-source s MIT licencí, zdarma.