Záznam z přednášky pro konferenci SQL Server Bootcamp 2023, kterou pořádal WUG Česká Republika v Brně 13. září 2023.
Author Archives: Robert Haken
Perly code-review – záznam a slides [Robert Haken, WUG Days 2023.2 Brno]
Slides z mé přednášky 12. září 2023 pro konferenci WUG Days 2023.2 Brno:
.NET Performance Tuning – záznam a dema [Robert Haken, WUG Days 2023.2 Brno]
Záznam z přednášky pro konferenci WUG Days 2023.2 Brno z 11. září 2023.
Starší provedení přednášky, kde je méně profilingu a více ukázek kódu, najdete též na YT:
Demo samotné najdete tradičně na GitHub: https://github.com/hakenr/PerformanceTuningDotnetCoreDemos
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):
- 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
DataContractpro 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í, zdeTemperatureFje 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
DateOnlyjsme 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.
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.tsv Microsoft.AspNetCore.Components.WebAssembly.Authentication,AuthenticationService.tsv 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):