Category Archives: Development

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.

Azure OpenAI chat (Global Azure Keynote) – záznam a demo [Robert Haken, Global Azure Praha 05/2023]

V pátek 12. května jsme společně s Davidem Gešvindrem (WUG) a Tomášem Hercegem (Update Conference) pořádali v pražském Microsoftu lokální běh celosvětové konference Global Azure.

Záznam z keynote, kde jsem v podstatě celou dobu mluvil o Azure OpenAI a vytváření chatbota v ChatGPT.

Chatbota si můžete vyzkoušet na chat.havit.cz.

Demo samotné najdete tradičně na GitHub: github.com/havit/HavitOpenAIChatPOC

Pokud máte zájem o něco podobného, ať už od nás naprogramovat, nebo jako pomoc vašemu IT týmu, ozvěte se nám havit.cz/kontakty.

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.

Základní Azure PaaS služby [Jiří Kanda, Vzdělávací okénko, 29.3.2023]

Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jak v HAVITu používáme platformní služby Azure pro hosting aplikací.

Slides

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.