gRPC code-first pro Blazor WebAssembly front-end

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):

  1. 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.
  2. 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í, zde TemperatureF je počítané a dopočítá se nám z ostatních údajů na klientovi kdykoliv znovu). Každému prvku je potřeba nastavit Order, kterým se definuje pevný layout pro používanou protobuf serializaci.
  • Původní typ DateOnly jsme vyměnili za DateTime. 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:

Obojí open-source s MIT licencí, zdarma.

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s