Domů Náš blog Blazor - část 1. - úvod
Blazor - část 1. - úvod
7. 1. 2020
Petr Otych
Blazor je jedna z nejzajímavějších událostí posledních let na poli vývoje webový aplikací. Tento framework a WebAssembly mají před sebou bezesporu světlou budoucnost. Co to ale vlastně je, na čem je postaven a jak se v něm vyvíjí?
WebAssembly
Musíme začít trochu zeširoka. Trendem posledních let na webu jsou aplikace s nějakým JavaSciptovým frameworkem na front endu. Již pár let se ale kutí nová technologie, která se na konci roku 2019 dostala do stavu webového standardu – WebAssembly. Nemá cenu si nějak obšírně vysvětlovat, co tento standard (zkráceně Wasm) popisuje, zásadní cíl je jednoduchý – bytecode v prohlížeči. Ten může být i v binárním formátu a jde zpracovat a spustit rychleji než kód v JavaScriptu.
WebAssembly však určitě nesmaže JavaScript z povrchu zemského, jde spíše o doplnění k němu než o jeho alternativu. JavaScipt s námi, již z jeho historického hlediska, zůstane ještě dlouho. WebAssembly navíc nyní neumí přistupovat přímo k DOMu, k tomu se musí dostat přes JavaScript- to se možná v budoucnosti změní, ale je otázka, jestli to je špatně. Wasm běží ve stejném security contextu jako JavaScipt, takže nemusíte mít o bezpečnost strach – nejde o něco jako Silverlight nebo Flash.
WebAssembly je podporováno ve všech moderních prohlížečích (takže ne v IE) a podporuje ho již řada compilerů. A tak si ho již můžete vyzkoušet s kódem napsaným v C++, Rustu a dalších. Ale nejen to – i řada interpretovaný jazyků má již interpret napsaný ve Wasmu (např. Python).
A co .NET?
Tak, to je sice super, že kód napsaný v C++ spustíte v prohlížeči, ale při vývoji webové aplikace to úplně nepomůže. Microsoft se ale této příležitosti chytil a rozhodl se přispět svojí troškou do mlýna. Ideální řešení by bylo zkompilovat .NET Core webovou aplikaci v C# do Wasmu, no ale aktuální stav CoreRT a obecně přístupu .NETu k AOT kompilaci je neslavný (uvidíme v době vydání .NET 5 a po něm) a tak nezbývalo než sáhnout po runtimu, co má v sobě JIT kompilaci a zkompilovat do WebAssembly jen ten - tzn. samotná aplikace zůstává v .NET IL. Microsoft si po prvních experimentech vzpomněl na Mono a bylo rozhodnuto. Od té doby již i AOT kompilace udělala pár kroků vpřed, takže bychom se opravdu mohli jednou dočkat, ale toto je prostě v mnoha ohledech běh na dlouhou trať.
Blazor
No a teď se konečně pomalu dostáváme k Blazoru. .NET Core nám běží v prohlížeči a potřebujeme něco, co to celé spojí dohromady s DOMem a umožní nám to aplikace vyvíjet. A máme Blazor – framework pro vývoj webových aplikací pomocí C# a Razoru. A protože WebAssembly neumožňuje přímý přístup k DOMu, tak si Blazor udržuje virtuální DOM, který synchronizuje se skutečným DOMem přes JavaScript.
Hosting modely
Server-side hosting model
Označované jako Razor components, ale i Blazor Server nebo Blazor components (loni se hodně přejmenovávalo, tak si vyberte). Ve finální verzi je od uvedení ASP .NET Core 3.0. Místo ve WebAssembly beží aplikace na serveru a komunikace mezí ní a prohlížečem (který tedy slouží jen pro zobrazení a UI) probíhá přes SignalR. Každý uživatel aplikace má tedy vlastní instanci této aplikace na serveru. Není to zase tak výkonnostně omezující, jako by se na prví pohled zdálo, ale pochopitelně zde má dopad i latence na server a jeho dostupnost. Na druhou stranu kód běží vždy jen na serveru, což je z bezpečnostního hlediska pro mnohé důležité. Popravdě řečeno, je to však spíše takové řešení, které vzniklo, protože k tomu okolnosti vybízely – interaktivní komponenty bez nutnosti JavaSciptu, původně nejistá budoucnost ohledně Blazoru ve WebAssembly a existující nutnost synchronizace virtuálního DOMu se skutečným. Tak do toho kotlíku Microsoft přidal SignalR, trochu to dovařil a máme Blazor na serveru. Jestli vám bude chutnat, je na vás.
Client-side hosting model
Zatím v preview, hotovo by mělo být tento rok. Funguje, jak bylo popsáno výše – runtime je ve WebAssembly, zkompiluje aplikaci z MSIL a ta běží přímo v prohlížeči. Soubory nutné pro spuštění aplikace mohou být jen staticky nahrané na webovém serveru, ale na vše sofistikovanější budete stejně potřebovat nějakou serverovou aplikaci, která mimo jiné zařídí i to hostování souborů. Od ASP .NET Core 3.1 podporuje Mono na WebAssembly již .NET Standard 2.1. Dříve bylo nutné kvůli podpoře jen .Net Standardu 2.0 vytvářet poměrně divoké hierarchie závislostí projektů, abyste se s tímto omezením popasovali. Nyní již v aplikaci můžete použít doslova celý .NET Core.
Jak začít s Blazorem?
Určitě se mrkněte na ukázkové aplikace ve Visual Studiu a prostudujte dokumentaci. Bylo by zbytečné se tady rozepisovat o všem, co Blazor nabízí. Spíše se v tomto a budoucích článcích zastavím jen u pár věcí.
Architektura Blazor aplikací
Obecně není definován architektonický vzor, kterým se Blazor řídí, ale nebudeme si nic nalhávat – Blazor má určitě blíž k MVC než k MVVM (ale i do toho se dá „ohnout“). Pokud se chcete striktně nějakého vzoru držet, tak se jistě objeví nějaké knihovny nebo postupy, ale v Blazoru jde s klidným srdcem vyvíjet již nyní – stačí prostě akceptovat, že si bere ze všeho něco.
Komponenty
Pokud jste někdy pracovali s nějakým frameworkem, co používá komponenty, tak vás Blazor v ničem nijak zásadně nepřekvapí. Celé to je o jejich vnořování a vkládání jejich hierarchií do stránek a layoutů.
Komponenty se píšou buď přímo v C# nebo Razoru. Pochopitelně to není ten úplně samý Razor, který znáte z Razor Pages nebo MVC, ale princip zůstává – je to předloha, ze které se vygeneruje třída komponenty. Stačí v zásadě pochopit, že blok markup kódu v .razor souboru kompiler vezme a vygeneruje z něj metodu pro vykreslení komponenty. Takže každou existující komponentu můžete vzít, zdědit z ní a přepsat její metodu vykreslení. To se hodí pro upravení již existujících komponent. Vygenerované třídy komponent jsou navíc partial, což je ideální, pokud nechcete mít klasický C# kód u markup kódu dané komponenty – prostě použijete druhý soubor.
Blazor se při překreslování DOMu snaží pracovat co nejefektivněji, ale za to po vás něco chce. Všechny komponenty a elementy mají id, které vzhledem k jejich rodiči musí být unikátní, a navíc mezi jejich sourozenci ve vzestupné posloupnosti – toho se musíte držet, pokud vykreslovací funkci komponenty píšete ručně. Blazor je díky identifikátorům schopen rychle zjistit změny v DOMu a to, co může použije znovu bez nutnosti smazání starých a vytvoření nových položek stejného typu – prostě do nich znovu pošle vstupní parametry. To vás může nemile překvapit, pokud má komponenta vlastní vnitřní stav, který na vstupních parametrech nezávisí – vnitřní stav vám zůstane, ale parametry se změní na parametry komponenty, co měla toto id při minulém vykreslení (typicky v kolekcích). Naštěstí lze dát komponentám i unikátní klíč (atribut key), který zajistí, že se parametry namapují vždy na tu stejnou komponentu.
Nebudu zde zbytečně popisovat, jakými fázemi existence komponenty procházejí, doporučuji si k tomu spíše najít nějaké názorné obrázky. Navíc jsou názvy těchto metod celkem vypovídající. Avšak přesto jen pár poznámek k tomu, až si to budete zkoušet:
-
SetParametersAsync: hlavní funkce komponenty, vnitřně volá ostatní metody, tzn. musíte zavolat implementaci rodiče a to synchronně, tzn. před tímto voláním nemůže být await. Reálně použitelné tedy hlavně na výjimky, když nejsou některé parametry nastaveny.
-
OnInitializedAsync a OnParametersSetAsync: tady je nutné si uvědomit, jestli s komponentou pracujete tak, jak se sluší a patří. Berte na vědomí, že vstupní parametry se mohou změnit a komponenta na to musí korektně reagovat. Pokud i ty operace, co jsou závislé na vstupních parametrech, provedete v OnInitializedAsync, tak se vám to časem vymstí. Dále je nutné upozornit, že pokud jsou volání těchto dvou funkcí opravdu asynchronní a vrátí tedy nedokončený Task, tak je komponenta ihned vykreslena – tzn. vykreslí se v takovém případě jakoby jednou navíc oproti tomu, než kdyby se metoda dokončila synchronně a celý životní cyklus komponenty se dokončil synchronně.
-
OnAfterRenderAsync: vrácený Task Blazor neawaituje, jedná se fire-and-forget, ale s tím, že výjimky v něm Blazor naštěstí korektně odchytí.
Komunikace mezi komponentami
Blazor má bezproblémově vyřešeno jednosměrné zasílání parametrů do vnořených komponent. Buď parametr zašlete jen do přímého potomka, nebo do všech vnořených potomků (tzv. kaskádové parametry) a vnořené komponenty si je odchytí buď podle typu nebo jména. Kaskádové parametry se mi jeví jako takové nemastné neslané – zaprvé nepřispívají čitelnosti kódu a zadruhé se jejich použití může lehce zvrtnout do situace, kdy defacto nahrazují vnitřní stav aplikace, který by měl být řešen něčím sofistikovanějším. Prostě platí klasika – všeho používejte s mírou. Blazor sám o sobě neřeší nějaký společný store/stav aplikace. Buď sáhnete po nějakém hotovém řešení, nebo si vystačíte s vlastním řešením přes nějaký singleton se zasíláním eventů do komponent.
Obecně všechny události v Blazoru jsou řešeny pomocí struktury EventCallback, která zvládne synchronní i asynchronní operace a zároveň oznámí komponentě, že došlo ke změně jejího stavu a ta se poté překreslí. Při překreslení komponenty se překreslí i její vnořené komponenty, které používají nějakou její proměnnou jako vstupní parametr.
Blazor zvládne i two-way binding. Jde však jen o one way binding s tím, že Blazor navíc vytvoří výše zmíněný EventCallback (defaultně se stejným názvem jako proměnná se sufixem „Changed“) s akcí, která nastaví změnu hodnoty proměnné v rodičovské komponentně. Tento callback musíte zavolat ručně – buď v nějaké funkci při každé změně hodnoty, což není úplně intuitivní, anebo v setteru. Avšak na to musíte mít duplicitní property, aby se vám to celé nezacyklilo (setter informuje rodiče, ten se překreslí a chce překreslit i vnořenou komponentu (protože existuje binding), nastaví mu parametr, a tak stále dokola). Navíc tento callback jde spustit jen asynchronně (z principu – jak jinak, když zvládne i asynchronní operace, ale to je v setteru vskutku nemilé), takže si musíte vybrat mezi F&F nebo synchronním spuštěním této metody. Ono je to ve skutečnosti jedno, protože to stejně dojede synchonně. Takže si vyberte, co vám jde visuálně méně proti srsti.
Takže ano, two binding v Blazoru je a lze jej použít skrz více potomků, ale je to takové kostrbaté. Co však kvituji je, že při two-way bindingu Blazor navíc nastaví i další volitelný parametr, a to se stejným názvem jako bindnutá proměnná se sufixem „Expression“. Ten musí být typu Expression<Func<T>>. A k čemu to je? No, protože to je expression se vždy stejnou lambdou, ve které figuruje jen ona proměnná, tak si z něho můžete vytáhnout jméno této proměnné. Což se vám v mnoha případech bude hodit (např. při práci s formuláři) a nemusíte název proměnné předávat explicitně.
Blazor a vlákna
WebAssembly zatím nepodporuje multithreading a to znamená, že Blazor v prohlížeči má k dispozici jen jedno vlákno. To se sice nehlásí jako thread-pool vlákno, ale jmenuje se tak a taky se tak naštěstí chová. Když máte v aplikaci jen UI vlákno a prázdný threadpool, tak si moc srandy s asynchronními funkcemi neužijete, protože se nemají kde spustit. V Blazoru tedy nepřekvapivě asynchronní funkce můžete používat, ale nemůžete na ně synchronně čekat (což je možná dobře – alespoň zjistíte, kde máte ve svých knihovnách přešlapy), protože prostě ty dvě vlákna, kde by jedno čekalo a druhé pracovalo, k dispozici nemáte a dojde k deadlocku.
V server-side Blazoru je situace zase trochu odlišná. Tam pochopitelně s thread poolem problém nemáte, ale vrací se nám starý známý z ASP .NET – synchronizační kontext. Blazor ho potřebuje, aby jím chránil práci s DOMem a platí pro něj stejné omezení jako v ASP .NET - jedno vlákno v jeden konkrétní čas. Takže pokud provádíte operaci ve vláknu s tímto synchronizačním kontextem, tak na jiné, které ho potřebujete, čekat nemůžete. Resp. můžete, ale opět dojde k deadlocku.
Blazor a vlákna
V rychlosti jsme si proletěli úvod do Blazoru a jeho základní principy. Určitě jej vyzkoušejte, protože to je bezesporu zajímavá technologie. Co je však na něm nejzajímavější jsou jeho dopady na vývoj aplikací. Jaké má možnost psát front end v C# dopady do celkového návrhu aplikace a na co se při jejich vývoji zaměřit, včetně rychlého zmínění další funkcí Blazoru a jeho budoucnosti, si řekneme někdy příště.