Category Archives: Blazor

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.

Blazor WASM: Nepodporované připojení k AAD pomocí standardní auth-knihovny (Microsoft.AspNetCore.Components.WebAssembly.Authentication)

Pokud jste si (podobně jako já) slibovali, že „snadno“ vytvoříte Blazor aplikaci, která bude napojitelná na obecného OIDC identity-providera pomocí knihovny Microsoft.AspNetCore.Components.WebAssembly.Authentication, přičemž budete zároveň touto cestou podporovat i napojení na Azure Active Directory (AAD), zklamu vás.

Myšlenka je jednoduchá, AAD přeci podporuje OIDC a tedy nyní důvod, proč by obecný OIDC klient neměl jít připojit i na AAD. Bohužel překážek je více, než byste chtěli překonávat.

Knihovna Microsoft.Authentication.WebAssembly.Msal, která je pro AAD určená, je sice nadstavbou základní Microsoft.AspNetCore.Components.WebAssembly.Authentication knihovny, dělá toho však o dost více, než jenom boilerplate konfiguraci podkladového obecného OIDC.

Základní rozdíl můžete najít například v „interop“ části s podkladovým JavaScriptovým modulem oidc-client.

  • AuthenticationService.ts v Microsoft.AspNetCore.Components.WebAssembly.Authentication,
  • AuthenticationService.ts v Microsoft.Authentication.WebAssembly.Msal.

Projevuje se to například při vyzvedávání access-tokenů. MSAL verze „fixuje“ specifikum AAD, které při dotazech na token endpoint ne vždy vrátí access-token se všemi požadovanými scopes (detaily jsou na širší diskuzi, ale např. nedostanete token, který by v sobě měl User.Read scope společně s custom-scope vašeho API, atp.).

Základní knihovna využívá „cachování“ tokenů v UserManageru oidc-client a spoléhá se na předpoklad: „Pokud token-endpoint vrátil access-token, pak tento token v sobě má všechny požadované scopes.“ (tedy si k získanému tokenu požadované scopes uloží a pak při dalším požadavku na stejné scopes vrací již získaný token).

„Fixovaná“ knihovna MSAL tento nedostatek podkladového oidc-client zná a ví, že tento sice tvrdí, že má token pro nějakou sadu scopes, ale access-token ve skutečnosti tyto scopes mít nemusí. Proto „vypíná“ na této úrovni cachování a získává token vždy znovu.

Novinky v C# 11, [ASP].NET 7 a Blazor – záznam, slides a dema [Robert Haken, FreshIT Praha 2023]

Záznam z přednášky pro konferenci FreshIT Praha z 30. března 2023, kde jsem povídal o novinkách v platformě .NET v poslední verzi 7.

Dema

Slides

Havit.Blazor – založení projektu z šablony [Robert Haken, Vzdělávací okénko, 22.3.2023]

Záznam ze Vzdělávacího okénka HAVIT z 22.3.2023.
Povídal jsem o založení nového projektu (solution) z šablony https://github.com/havit/NewProjectTemplate-Blazor.

Podrobnosti o Havit.Blazor stacku viz záznam přednášky pro WUG Zlín.

Havit.Blazor stack podrobněji – záznam z WUG Zlín [Robert Haken, 28.2.2023]

Záznam z přednášky pro komunitní setkání WUG Zlín z 28. února 2023. Povídal jsem o uspořádání Havit.Blazor stacku (šablona https://github.com/havit/NewProjectTemplate-Blazor) a proč je uspořádaný, tak jak je.

Slides (PDF):

Havit.Blazor stack – záznam z WUG Days 2023.1

Záznam z přednášky pro konferenci WUG Days 2023.1 v Brně z 30. ledna 2023. Povídal jsem o uspořádání Havit.Blazor stacku (šablona https://github.com/havit/NewProjectTemplate-Blazor) a proč je uspořádaný, tak jak je.

Slides (PDF):

Blazor – životní cyklus komponent II [Jiří Kanda, Vzdělávací okénko, 1.12.2021]

Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval již podruhé životní cyklus Razor (Blazor) komponent. Tentokrát se soustředil na detekci změn parametrů komponent.

HAVIT Blazor – Bootstrap 5 Components Bundle [Robert Haken, Vzdělávací okénko, 13.10.2021]

Záznam ze Vzdělávacího okénka HAVIT, kde jsem nechával nahlédnout pod pokličku vývoje naší sady komponent pro Blazor, jak jsme daleko, jak si stojíme, co to umí, co nás ještě čeká, atp.

https://havit.blazor.eu