14.09.2022 | 

Webinar: Blazor WebAssembly: Performance-Optimierungen fürs UI

Stockende UI, langsame Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld – dies sind nur wenige Beispiele für alltägliche Probleme, die beim Benutzen von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen immer wieder auftreten können. Bei der Entwicklung von SPAs, und so auch bei Blazor WebAssembly SPAs, ist es wichtig, die Laufzeit-Performance der Anwendung immer im Auge zu behalten und wenn nötig zu optimieren.

Moderation

Marco Frodl

Marco Frodl

Marco Frodl ist Consultant bei der Thinktecture AG und versteht sich als ein IT-Dolmetscher zwischen Developern und Anwendern.

Agenda

  • Motivation
  • Render-Lifecycle
  • Komponentenoptimierung
  • Event-Handling
  • Listenoptimierung
  • Resümee
  • Q&A

Material & Video zu
"Blazor WebAssembly: Performance-Optimierungen fürs UI"

Blazor WebAssembly: Performance-Optimierungen fürs UI (Webinar vom 14.09.2022)

Einen wunderschönen guten Morgen und herzlich willkommen zum heutigen Thinktecture Webinar. Mein Name ist Marco und bei mir ist heute der Patrick. Hallo Patrick.

Guten Morgen Marco. Und auch Guten Morgen euch da draußen.

Schreibt uns bitte mal in den Chat, wenn alles wie gewohnt funktioniert. Ich bin gerade erst aus dem Urlaub zurück. Ich weiß gar nicht, ob das alles noch funktioniert. Ob es noch wie gewohnt das macht, was es soll. Heute wird es schnell und laut. Schnell, weil es um Performance gehen soll, nämlich wie man Blazor WebAssembly unter Umständen noch ein bisschen mehr Speed verpassen kann, wenn man Applikationen damit programmiert und laut: das liegt eher an mir und nicht an Patrick. Bei mir wird hier gerade Glasfaser verlegt und zwischendurch hört man dann irgendwelche Bohrhämmer, die sich durch Wände fressen. Ich hoffe, das erwischt uns jetzt nicht so so heftig gleich am Anfang. Zwischendurch werde ich mich dann eher muten. Und Patrick soll sowieso das meiste erzählen - er ist der Experte für das Thema. Aber das heißt, in Zukunft kann ich dann in 8K auf eure Monitore kommen, wenn alles gut läuft und habe Bandbreite ohne Ende. Insofern zu Fortschritt gehört, dann offensichtlich auch Lärmbelästigung. Alles klar, dann lasst uns mal kurz damit starten, das ich euch gerne zeigen würde - für die die noch nicht dabei waren - wie das ganze hier heute funktionieren soll.

Dazu habe ich kurz schon was vorbereitet. Wenn ihr später Fragen habt, schreibt die bitte in den Q and A Tab, den er oben rechts neben dem Chat findet. Das hat den Vorteil, dass die anderen Zuschauer und Teilnehmer da voten können. Und damit ergibt sich eine Rangfolge. Das heißt, wenn wir so nach 60 Minuten an den Punkt kommen, wo der Teil von Patrick vorbei ist und wir diesen optionalen Q and A-Teil machen, dann wissen wir, welche Fragen da die spannenden sind. So dass wir nicht dann noch heute Nachmittag hier sitzen und Sachen diskutieren. Wenn ihr die wichtigste Frage schon beantwortet haben wollt, dann ist die Antwort hier: Wird das Webinar aufgezeichnet. Kriege ich das Material? Die Antwort lautet in beiden Fällen "Ja". Ihr findet das früher oder später auf unserer Webseite unter Thinktecture.com/Wissen/Webinare. Da gibt es dann später die Aufzeichnung, sobald wir das so ein bisschen hübsch gemacht haben, da liegen auch die Slides, da liegt auch Link zum Code Repository und alles weitere. Das heißt, ihr könnt euch bequem in euren Stuhl zurücklehnen und könnt einfach der Sache folgen oder wild im Chat kommentieren und euch beteiligen.

Das heißt, ihr müsst ja nicht zwischendurch irgendwie mittippen. Das machen alles wir für euch. Am Ende haben wir dann eine Frage an euch. Da gibt es eine kurze Umfrage und hier würde uns neben der reinen Bewertung auch interessieren, ob ihr weitere Themen habt. Konkrete Anregungen, zu denen ihr gerne mal ein Webinar von einem unserer Experten sehen wollen würdet. Dann könnt ihr uns das da ins Anmerkungen-Feld schreiben. Und wenn ihr genau mit 60 Minuten jetzt geplant habt und sagt nachher ich kann den Q&A-Teil jetzt nicht abwarten, ich muss vorher raus, dann findet ihr oben diesen Button mit den drei Punkten mit der Möglichkeit "Exit Webinar" früher auszulösen, kommt in diese Umfrage und könnt euer Feedback auch hinterlassen, selbst wenn ihr schon ins nächste Meeting vielleicht weiter müsst. Ist gar kein Problem. Für alle, die Fragen haben, die zum einen nicht in dieses Q and A Input Feld passen, weil zu lang oder weil es sensitive Informationen gibt, haben wir was schönes für euch. Später im Webinar, wenn wir einen Link posten zu der Möglichkeit ein Vier-Augen-Gespräch mit dem Patrick zu planen und das gibt euch die Möglichkeit in einem 20 Minuten Slot dann einfach auch mal Screen Sharing auf eurer Seite zu aktivieren, irgendwas zu zeigen, irgendwas zu Performance zu fragen.

Das ist eine ganz coole Geschichte, weil das würde sonst hier auch den Rahmen sprengen. Bzw sind da oft natürlich auch sensitive Geschichten dabei, die dann nicht geteilt werden können. Und das ist dann unser Angebot, wenn ihr im Nachgang ein bisschen komplexere oder speziellere Fragen habt. Blenden wir zwischendurch ein - also aufmerksam bleiben, wenn die Stelle kommt, damit ihr das nicht verpasst. Und damit würde ich sagen Patrick, bin ich mit dem Introteil "Alles, was man wissen muss" schon durch. Ich sage noch mal Guten Morgen nach Rottweil, nach Breisgau, Hannover - auch schön. Trossingen - das klingt sehr weit südlich. Und wünsche euch viel Spaß und sage Bühne frei für dich, Patrick.

Alles klar dann. Noch von mir: Einen schönen guten Morgen und herzlich willkommen zu meinem Webinar heute. Ich möchte euch mal so ein bisschen Einblick geben über Performance Optimierung in der UI mit Blazor WebAssembly. Und bevor wir heute mit dem Thema durchstarten, auch mal kurz Hintergrund-Backup zu mir. Da gibt's nicht viel zu sagen. Ich bin Developer Consultant bei der Thinktecture AG, bin hier im Fullstackbereich unterwegs, das heißt ich arbeite sowohl im Backend als auch im Frontend, was mit Blazor natürlich die Liebe schlechthin ist, weil man ja im Backend Bereich .Net und im Frontend-Bereich .Net machen kann. Das passt einfach wunderbar. Zusätzlich mache ich im Frontend-Bereich auch noch Angular, das heißt auch die JavaScript Welt ist mir nicht fremd, was ich beim Blazor WebAssembly das eine oder andere Mal dann doch noch gebrauchen und einsetzen darf. Wer mich im Nachgang irgendwie erreichen möchte: gar kein Thema per Email auf die [Emailadresse] oder via Twitter direkt einfach antwittern mit @Jahr_Patrick. Und wenn ihr andere Sachen oder wenn euch Blazor interessiert oder noch mehr über mich wissen wollt, könnt ich das gerne auf der Thinktecture.com auf der Profilseite nachschauen. Da gibt es einige Artikel von mir, vor allem zum Thema Blazor und auch die Webinare, die ich bereits gehalten habe. Und da kommt natürlich auch was von heute noch dazu. Bevor ich jetzt einsteige in das Thema und euch auch mal zeige "Was wollen wir denn heute überhaupt anschauen?" würde mich jetzt im ersten Moment interessieren: wer hat denn schon mal mit Blazor WebAssembly gearbeitet und wer hat das schon mal produktiv eingesetzt? Oder hat es tatsächlich schon in Anwendungen im Einsatz? Wer hat mal so eine Demo ausprobiert? Hat gemeint Hey, cool, dass du dich mal ein bisschen mehr zu erfahren, oder wer sagt, ich kenn mich da noch gar nicht aus, aber man kann ja mal reinschauen. Klingt spannend. Gut, dann zeige ich euch mal die Demo, die ich vorbereitet habe. Da switche ich jetzt gerade mal kurz zu Visual Studio und drücke hier auf den Start-Button. Was passiert hier? Hier wird eine API hinten dran gestartet und meine Anwendung startet, die wir uns heute ein bisschen näher anschauen.

Wir sehen jetzt hier eine ganz normale Liste von Conferences und Contributions, die vorbereitet habe. Diese Webseite ist so aufgebaut, dass wir immer mal wieder so ein bisschen Information bekommen, was hier gerade passiert. Zum Beispiel mein Rendering Count. Wir haben die Zeit, wie oft was gerendert wird bzw. wie lange es dauert, bis was gerendert wurde. Ihr seht hier schon, wir haben so ein bisschen ein Highlighting drin mit einer SignalR-Verbindung, das heißt es kommt ein Realtime Push rein, der dann dementsprechend die Liste aktualisiert, was so oft dazu führt das ein Rendering durchgeführt wird. Dann habe ich noch eine Liste an Contributions, die auch ziemlich lange dauert, würde ich sagen, bis sie mal gerendert ist. Und auch hier können wir runterscrollen. Es ist alles ein bisschen träge und wenn ich jetzt hier zum Beispiel den Dialog öffnen will, ihr habt es vielleicht gesehen, das ist jetzt nicht durch die Videoübertragung, das ist relativ langsam, auch wenn ich jetzt hier auf Abbrechen klicke, das dauert dann schon mal die eine oder andere Sekunde, bis das Fenster überhaupt zu ist.

Ich erkläre euch später, warum das so ist. Und das ist das, was wir auch anschauen wollen. Was wir hier zum Beispiel auch noch sehen, wenn ich in einer Textbox schreibe, wie oft eine Komponente eigentlich gerendert wird. Auch das ist ein spannendes Thema, das wir uns heute näher anschauen werden. Gut, dann beende ich das Ganze hier auch mal wieder, weil das ist jetzt nur zur Demo gewesen, damit ihr dann mal seht, was wollen wir heute überhaupt anschauen. Und dann gehe ich zurück in die Slides und schau mal nebenher auf den Poll was wir den heute so haben. Ok, wir haben...

67% Anwender von Blazor WebAssembly. Das ist gut, oder?

Das ist sehr gut und sehr erfreulich und es freut mich immer wieder, wenn Leute das schon im Einsatz haben, weil ich auch selbst weiß, es ist noch ziemlich früh. Es ist noch ein sehr junges Framework, wie jedes andere Framework auch mal war, aber ich find es trotzdem einziemlich cooles Framework und ich freue mich immer drauf, wenn auch neue Sachen kommen. Von daher finde ich cool, dass wir hier schon bei zehn Leuten sind, die das ganze Einsetzen sogar schon produktiv und zwei sogar schon demo technisch. Und die anderen zwei, die auf nein geklickt haben, die kann ich heute hoffentlich davon überzeugen, mal ein bisschen näher drauf und rein zu schauen und sich das ganze mal anzuschauen. Gut, aber was haben wir heute so vor? Liste ist ganz klar: Wir haben gesehen, die Anwendung besteht eigentlich aus einer Liste. Daher ist wichtig das Thema Blazor WebAssembly: Listenvirtualisierung. Wie kann ich meine Liste beschleunigen? Wie kriege ich das Ganze verbessert, dass nicht so oft gerendert werden muss, dass nicht so lang gerendert werden muss.

Da ist die Listenvirtualisierung ein ganz großes Thema. Dann natürlich der Render Prozess. Was passiert denn überhaupt, wenn eine Komponente gerendert wird? Wie sieht da der Prozess aus? Was kann man da tun? Und da kommen wir dann zum nächsten Punkt mit cleveren Komponenten. Das heißt, wie kriege ich meine Komponente so clever, dass sie sagt okay, ich rendere mich nur dann, wenn ich auch wirklich gerendert werden möchte und ich rendere mich nicht einfach nur, weil ich es kann. Danach ein sehr spannendes Thema Events-Reaction. Das heißt, was passiert eigentlich, wenn ein JavaScript-Event, also ein DOM-Event passiert? Wie verhalten sich da die Komponenten? Bzw wie verhält sich hier die Blazor WebAssembly-Anwendung und was kann man dagegen tun? Zum Abschluss gibt es dann noch ein kleines Resümee von mir und dann sind wir auch schon durch. Dann möchte ich mal in die Runde fragen: Wer hat denn schon mal mit Performanceoptimierung oder wer hat Performanceprobleme grundsätzlich schon mal Blazor WebAssembly gehabt - jetzt spezifisch auf die UI gesehen, also dass heißt die Komponenten, die Listen oder ähnliches.

Gibt es da schon Leute, die damit Probleme hatten? Es würde mich mal interessieren und ich glaube, dafür haben wir auch einen Poll, den wir jetzt starten können. Und da bin ich mal gespannt, wie so die, wo die Reise hingeht.

Da ja viele schon damit gearbeitet haben, wäre jetzt die Frage: Haben diejenigen schon gemerkt, dass Performance etwas ist, was sich dann in bestimmten Aspekten zeigt? Oder ist es was, wo alle sagen: Für mich war es immer schnell genug!? Dann sind die Anwendungen vielleicht anders geschnitten oder haben nicht diesen natürlich auch künstlich aufgeblähten Umfang, Patrick, den du jetzt in die Listen der Demo reingepackt hast. Du machst es ja auch extra schwer der Demo Anwendung, damit sie so ein bisschen in die Knie geht. Da bin ich mal gespannt. Das ist in den Pollergebnissen ein bisschen anders. So 50:50 also die die ersten haben schon gemerkt, gibt Stellen, da fühlt sich ein bisschen zäh an, die sind heute dann genau richtig hier, um zu sehen, wie man es besser machen kann. Und die anderen, die es noch nicht gemerkt haben, sind eigentlich auch richtig. Du gibst die Rezepte raus, damit es gar nicht erst dazu kommt.

Genau wie Marco schon sagt 50:50 Und ja, es hat die Anwendung auch dementsprechend bisschen gepusht, damit hier auch mal Performanceprobleme zum Vorschein kommen. Ich meine, ich bin ja auch lokal auf der Maschine unterwegs, was natürlich an Performanceproblemen herauszufinden etwas schwierig ist, weil wer kennt es nicht "Running on my Machine" und dann publisht man das ganze und dann kommen erst die ganzen Performanceprobleme. Aber nichtsdestotrotz fangen wir jetzt mal an mit der Listenvirtualisierung und was meine ich mit Listenvirtualisierung. Und dazu gehe ich mal in die Slides wieder zurück und zeige da so ein paar Fragen, die ich mir da gestellt habe. Müssen denn alle Elemente in den DOM geladen sein? Warum frage ich mich das? Natürlich, wenn ich eine For-Each-Schleife oder ähnliches mache über meine Items, die ich geladen habe, dann sind die alle in meinem DOM. Das heißt alle werden in den DOM geladen, obwohl ich sie jetzt sehe oder nicht, weil mein View also mein Browserfenster ist ja nur so groß und kann dementsprechend auch nur ein paar Elemente anzeigen und lang nicht die tausend Elemente, die vielleicht in der Liste sind.

Daher müssen alle geladen werden. Dann die andere Frage: Wie schnell komme ich an meine Daten? Wie schnell ist meine API? Wie schnell ist der Server, wo die API läuft? Wie schnell komme ich da an die Daten? Gibt es vielleicht Pagings, ist da irgendwas möglich und da gibt es seit .Net 5 in Blazor, die Virtualize-Komponente und die Virtualize-Komponente unterstützt genau das. Die Virtualize-Komponente lädt nur das in den Sichtbereich, also das in das Browserfenster, was ich auch aktuell sehen kann. Das heißt, wenn ich jetzt 1000 Elemente habe und ich kann aktuell nur 25 sehen, dann werden auch nur die 25 Elemente geladen. Dann habe ich die Möglichkeit Lazy Loading zu machen. Das heißt, wenn meine API ein Paging anbietet, wäre hier die Möglichkeit, das Lazy Loading einzusetzen. Das heißt, ich scrolle und während dem Scrollen werden Daten nachgeladen. Während die API dann dementsprechend braucht, um die Daten zu liefern. Ist halt die Frage was zeige ich währenddessen an? Weil ein leerer Screen oder ein stockendes Scrollen ist auch ziemlich hässlich.

Und da gibt es jetzt die Möglichkeit in der Virtualize-Komponente zu sagen ich kann ein Ladeanimation reinpacken, damit der Nutzer weiß, okay, es tut sich hier was. Vielleicht ist meine Verbindung auch gerade nicht die Beste, aber ich weiß, da kommt jetzt noch was, da wird gerade noch was nachgeladen und das ganze sieht Browsers so aus, dass ich zwar den Sichtbereich geladen habe, aber auch vor dem Sichtbereich nach dem Sichtbereich schon Items geladen wurden. Weil ich kann ja langsam scrollen und dann brauche ich ja nicht direkt alles neu laden, sondern die Virtualize-Komponente sagt "Ok, ein bisschen was kann ich ja schon mal vorladen" - dann ist es direkt da, wenn ich anfange zu scrollen und alles, was dann über den Bereich hinausgeht - also das nennt man den Overscan Count. Also wenn der Bereich erreicht ist, dann werden die nächsten Items geladen, so dass das ziemlich smooth vorangeht. Soviel zur Theorie. Und das Ganze schauen wir uns jetzt mal in unserer Demo an dazu wechsle ich wieder in unser Visual Studio und ich zeige euch mal die Komponente, die ich dafür gebaut habe.

Das ist eine Collection-Komponente. Und was habe ich hier gemacht? Ich habe hier eigentlich eine ganz normale Tabelle aufgebaut. Also das ist einfach ein DIV mit einer Liste und darin habe ich einen Styles darüber gepackt und das ist das, was wir vorhin in der Intro in der Demo gesehen haben. Aktuell habe ich hier ein For-Each und in dem For-Each wird dann mein Inhalt gerendert. Den Inhalt habe ich als Render-Fragment übergeben, das heißt ich kann hier einfach den Inhalt übergeben, den ich angezeigt haben möchte pro Row. Und das sieht dann im Anwendungsfall zum Beispiel so aus Ich habe meine Collection, ich habe den Header Content, ich habe den Row Content. Hier ist auch eine Komponente drin und dann habe ich hier noch einen Placeholder, der wird später interessant, aber so wird die Komponente angewendet. Und jetzt schauen wir uns das Ganze mal in seinem Rohzustand im Browser an. Dazu starte ich die Anwendung und drücke mal F12, damit wird die Dev-Tools sehen. Aber mich mache die schon mal ein klein wenig größer.

Okay, da hat sich der Browser gerade verabschiedet. Muss ich mal abwarten, ob er gleich noch mal kommt und aha - jetzt ist er wieder da. Schauen wir mal in die Elemente und schauen uns gerade mal die Tabelle an und gehen mal in die Liste rein und ich scrolle jetzt hier mal runter und ihr seht, es sind schon ganz schön viel. Ganz schön sehr viele, so dass dieses Element sogar sagt "Okay, wenn du noch mehr sehen willst, dann drücke hier vorher erst auf den Button, weil mir reichts langsam". Also hier sind 1500 Zeilen drin, das ist schon sehr viel. Und dementsprechend ist auch hier wenn es scrollt und wir bekommen parallel eine Push Nachricht (via SignlarR), dann stockt es hier auch manchmal und bleibt dann kurz hängen. Und das ist einfach nicht schön. Es fühlt sich nicht gut an, wenn gerade mal alles smooth läuft, dann ist es okay. Aber das ist leider nicht immer der Fall. Und auch die Renderingtime jedes Mal ist schon ziemlich hoch Und ich würde sagen, die drücken wir jetzt schon mal drastisch nach unten, so dass ich sage, wir kommen, so im Bereich von 0,05 Sekunden. Also dass wir das Ganze mindestens bei der Renderingzeit halbieren.

Und das machen wir dafür? Dafür setzen wir jetzt die Virtualize-Component von Blazor WebAssembly ein. Das heißt, wir kommentieren hier mal das For-Each aus, das wir das mal vor Augen haben. Und dann öffne ich hier eine Virtualize-Komponente, die von Blazor WebAssembly bereitgestellt wird. Und ich packe hier gleich noch die Items rein. Items ist einfach eine Liste von T, das wäre jetzt in einem Fall die Contributions, im anderen Fall wären es die Conferences und was ich dann hier noch brauche ist der Context. Der Context wäre jetzt in dem Fall "Item". Das ist das gleiche wie jetzt hier in dem For-Each das "var". Das heißt, ich definiere hier einen Namen für meine Variable und ich sage das Ganze einen OverscanCount von 25 Elementen. So, dann kann ich sagen, ich habe eine ItemContent und der ItemContent wäre in dem Fall mein RowContent mit dem Item und das war es auch schon. Sagt mir hier natürlich seid .NET 6 schön "Rebuild And Apply Changes", denn wir haben Hot Reload, was natürlich uns das ganze auch deutlich vereinfacht, für die, die noch nicht Blazor WebAssembly kennen: Hot Reload seid .NET 6 ziemlich cooles Feature. Ich weiß, aus JavaScript-Frameworks kennt man das wahrscheinlich frei Haus, aber mit Blazor kam das erst in .NET 6 und jetzt schauen wir uns das ganze mal genauer an: Wir sehen hier, wir sind sogar bei 0,01 Sekunden. Das heißt wir haben das ganze wirklich drastisch nach unten gesetzt und ich lade das ganze jetzt mal neu, dass die ganze Liste geladen werden muss. Und wir sind bei 0,02 Sekunden für die ganze Liste. Jetzt ist die Frage: Warum ist es so performant und warum ist es so schnell? Jetzt schauen wir uns mal die Tabelle an und scrolle hier mal ein bisschen runter. Und wie wir sehen, hier ist kein Button, der sagt "Lade mal ein bisschen mehr", sondern nein, hier ist eine Height definiert und die Höhe ist berechnet und zwar anhand der Zahlen, die anhand der Rows, die er hier reingeladen hat. Und wenn ich jetzt scrolle, werdet ihr sehen, ist hier dann so eine leichte Lichtershow zu sehen. Das heißt, ich fange jetzt an zu scrollen und dann geht es hier schon los.

Was macht er hier? Er tauscht hier den Inhalt meiner Komponente aus, das heißt, er löscht nicht die Tabellenspalten also das Table Row, sondern er tut lediglich den Inhalt dieser Listen aktualisieren und lädt daher nicht das Ganze neu, sondern rendert so viele Spalten, wie er gerade braucht. Gut, sollte es jetzt nicht so viel sein, wie er gerade zur Verfügung hat, dann wird es ganz vielleicht kleiner oder höher, aber er kann dementsprechend scrollen und das Ganze ist wirklich smooth. Was habe ich noch für einen Vorteil dadurch? Weil wir haben vorhin gesehen, okay, ich habe jetzt hier ein Realtime-Push in unserer Blazor-App, aber ist der Realtime-Push notwendig an der Stelle, wo ich gar nicht bin? Also wenn ich jetzt einen Realtime-Push für das Item eins bekomme und ich bin jetzt hier bei 559, dann interessiert mich der Realtime-Push oben gar nicht, sondern ich bin hier und dann interessiert mich das Rendering auch nicht. Und wie ihr hier seht: das Rendering läuft zwar noch hoch, aber wir haben keine Scrolling-Probleme. Das läuft alles ziemlich smooth.

Gut an, so viel mehr zur Virtualize Component in Blazor WebAssembly. Jetzt kommt der Fall. Okay, ich habe jetzt 1500 Elemente geladen. Jetzt ist die Frage 1500 Elemente? Braucht man das wirklich? Also muss ich wirklich von Anfang an 1500 Elemente laden? Ich glaube nicht. Wenn ich eine API aufbaue, dann baue ich die meistens so auf, dass ich sage okay, 100 Elemente, das kann ich gerne mal übertragen, vielleicht auch 200 Elemente, aber alles was drüber ist, mache ich dann doch lieber mit Paging. Und auch das kann die Virtualize-Komponente in Blazor WebAssembly. Dazu gehe ich mal zurück in die Virtualize-Component bzw. in unsere Komponente und lösche hier mal das Items und sagt hier, ich hätte gern einen ItemsProvider und dann kann ich hier eine Methode übergeben "LoadCollection". Diese Methode habe ich natürlich aus Zeitgründen schon einmal vorbereitet, aber ich gehe jetzt hier mal rein und dann schauen wir mal, was diese Methode macht. Die Methode gibt einen "ItemsProviderResult" zurück. Dieses "ItemsProviderResult" ist eigentlich die Liste inklusive dem Count, den ich habe.

Das heißt, ich hole mir den aktuellen Count meiner Liste und berechne mir dann, was ich jetzt brauche. Wie kann ich das berechnen? Die Virtualize-Komponente gibt mein Requests rein und sagt okay, ich brauche diesen Count und das ist mein Startindex. Das heißt, ich kann schön in meiner API angeben, wie viel er überspringen soll, also wie viel er skippen soll und wie viel ich haben möchte. Das übergebe ich dann in meine API und dann bekomme ich die Collection zurück und kann sagen, so viel brauche ich. Und er lädt mir die Daten nach. Jetzt ist die Frage okay, was ist, wenn ich ganz schnell scrolle? Wenn ich weiter nach unten will? Dann haut er jedes Mal so ein Request rein und jedes Mal wird gegen die Datenbank gefeuert. Punkt 1: Ja, es wird gegen die Datenbank gefeuert. Punkt 2: Sobald er merkt, okay, ich scrolle das schneller, brichtet die Operation ab und führt den neuen Call aus. Das heißt, der Call wird einfach abgebrochen und sagt okay, das ist durch. Und ich hole mir schon die nächsten Daten.

Jetzt habe ich natürlich hier den Fall. Okay, ich habe ItemsProvider, ich lade meine Collections, das passt soweit alles. Und dann möchte ich natürlich, dass hier noch was angezeigt wird. Okay, wenn jetzt meine API einmal etwas länger benötigt - dafür nehme ich den Placeholder und nehme hier den "RowPlaceholder", dafür brauche ich kein Item. Dann schauen wir mal, ob das Ganze so funktioniert. Jetzt sind wir hier in den Conferences. Wir haben auch wieder eine sehr kurze Ladezeit. Das Ganze ist da. Und jetzt mache ich mal hier den Network Tab vom Browser auf und fange an bisschen zu scrollen.

Und ihr seht hier. Okay. Wir holen uns die nächsten Conferences. [...] Dann sehen wir hier, dass der Call hier abgebrochen wurde. Der ist rot. Also der ist Canceled. Wenn man jetzt den Status mal größer machen, dann sehen wir hier, der steht auf Canceled. Und dann hat er sich die nächsten geholt und hat hier geschaut. Okay, ich hätte gern 96 übersprungen und dann 57 Elemente. Und so kann er sich dann via Paging das Ganze holen. Und wenn ich schneller scrolle, sehen wir hier auch so eine kleine Ladeanimation, so dass man weiß, okay, da kommt jetzt noch was. Gut. Also ich würde sagen, hier kommt man mit Virtualize schon mal sehen: wir können die Rendering-Time deutlich drücken. Wir haben Paging, das automatisch mitkommt, wir haben Placeholder, der automatisch mitkommt. In der nächsten Version in .NET 7: was ich jetzt vorausschauend schon sagen kann, kommen auch noch Styling-Optionen dazu.

Das heißt, wenn man zum Beispiel ein Gap zwischen den Items hat. Das was ich jetzt hier mit CSS mache, kann man automatisch mit übergeben, so dass da auch das Styling angepasst werden kann. Von daher, auch die Komponente wird immer weiter gepflegt. Und dann ich sagen, stoppen wir hier noch mal, weil jetzt kommen wir in die nächste Lektion und dazu gehen wir wieder in die Slides und kommen jetzt zu den Render Prozess. Der Render Prozess ist eine spannende Geschichte bei Blazor WebAssembly. Aber zuerst mal, wann wird eine Komponente gerendert? Wir haben natürlich die Initialisierung. Ganz klar, wenn eine Komponente initialisiert wird, dann ist es das First Rendering und dann wird 100% gerendert. Dann haben wir den Fall natürlich, wenn sich Parameter ändern, dann wird die Komponente auch gerendert. Da haben wir schon den ersten Fall, wo man schauen muss: okay, wenn sich ein Parameter ändert, was für Auswirkungen hat der denn überhaupt? Das nächste ist Änderungen von Parametern in der übergeordneten Komponente. Das heißt, wenn wir eine Liste sind und wir haben RowItems, dann werden die Items jedes Mal gerendert, wenn sich zum Beispiel der Titel der Liste ändert.

Die Frage okay, spannend, aber warum? Dann als nächstes: DOM Events. Jedes JavaScript DOM-Event führt natürlich zu einem Rendering. Da gehe ich später noch ein bisschen näher drauf ein. Aber ihr habt es ja vorhin ganz am Anfang in der Demo gesehen, schon allein beim Titel wurde jedes Mal ein Re-Rendering getriggert. Und natürlich, wenn wir selbst wollen, dass was gerendert wird, kennt man die StateHasChanged-Methode, dass heißt die, die Blazor WebAssembly schon mal eingesetzt haben, denen ist es wahrscheinlich nicht unbekannt. Die, wo es noch nicht kennen: StateHasChanged - da triggert man das Re-Rendering der Komponente an, dass heißt es wird neu gerendert, weil man das Ganze haben möchte. Wann nutzt man das? Zum Beispiel wenn man interne private Variablen ändert, die Auswirkung auf die UI hat, könnte man das StateHasChanged aufrufen, um die UI zu aktualisieren? Gut, was passiert, wenn ein Re-Rendering oder wenn ein Rendering aktiviert wird? Da wird zuerst geprüft: ist es das initiale Rendern bzw das erste Rendern? Wenn ja, dann wird natürlich gerendert.

Wenn nein, gibt es in der Basisklasse für Komponenten die ShouldRender-Methode, die abfragt, soll dann meine Komponente gerendert werden. Die ShouldRender-Methode hat einen Default-Wert und der ist "True". Microsoft hat gesagt okay, wir packen True rein, das soll immer gerendert werden, egal was passiert. Sollte das ganze False zurückgeben, wird das Rendern beendet und alles ist vorbei. Im anderen Fall wird der Render-Tree erstellt. Da gibt es einen Render-Tree-Builder, der da durchgeht, sich den DOM zusammenstellt und das Update wird dann an den Browser gesendet und der Browser aktualisiert dann anhand dieses Trees den DOM und zum Abschluss wird dann in unserem .NET-Code die OnAfterRender-Methode aus dem Lifecycle aufgerufen, also der Komponenten-Lifecycle und die OnAfterRender-Methode ist der Abschluss sozusagen. Das heißt, wenn ich da angekommen bin, ist das Rendering vorbei. Gut, dann schauen wir uns mal an, wir clevere Komponenten in Blazor WebAssembly gestalten können. Clevere Komponenten, das heißt ShouldRender habe ich schon angesprochen, prüft, ob eine Komponente gerendert werden soll. Aktuell Default-Wert ist "True".

Das heißt, unsere Komponente wird immer gerendert. Aber da es eine Basisklasse ist und diese Methode nicht privat ist, haben wir die Möglichkeit, diese Methode zu überschreiben. Sie wurde nach außen freigegeben. Das heißt, wir können die ShouldRender-Methode überschreiben, unsere eigene Logik da reinpacken und das ganze selbst steuern. Das Zweite, was man machen kann, ist es, primitive Typen zu nutzen, wie zum Beispiel ein String und Int oder ein Bool, dann ist die Logik von Microsoft so aufgebaut, dass es deutlich einfacher zu erkennen ist, ob sich da etwas geändert hat. Dann wird es Rendering auch minimiert, aber nicht komplett abgeschaltet. Aufwändiger wird es natürlich für komplexe Typen, das heißt wir haben ganze Models, die wir als Parameter übergeben oder an wir übergeben ganz unterschiedliche Arten von Parameters, das heißt primitive und komplexe Typen. Und dann wird in diesem Fall - also im Standardfall - immer neu gerendert. So viel zur Theorie wieder und dann springen wir mal in die Demo und schauen uns das Ganze mal in in der Conference Row an. Die Conference Row ist einfach die Row unserer Liste.

Das sehen wir hier so ein paar Elemente mit dem Titel, dem Date und so weiter. Und hier oben ist so ein spannender Teil. Da geht es um die Highlight-Classes, also der blau hinterlegte Hintergrund, die ich setze, wenn die Conference aktualisiert wurde. Schauen wir uns das Ganze hier hinten dran mal an. Ich habe hier schon ein bisschen was vorbereitet und hier habe ich zum Beispiel diese Methode ShouldRender und ShouldRender habe ich jetzt hier auch default-mäßig "True" zurückgegeben. Und das ist jetzt der Punkt, den wir optimieren möchten. Das heißt, was kann ich hier jetzt tun? Ich überschreibe hierfür die OnParameterSet-Methode. Die OnParameterSet wird dann aufgerufen, wenn sich ein Parameter ändert. Das heißt, ich gehe hier rein und schaue mal, okay, ich hab hier die Conference-ID und die Highlight-ID. Die Highlight kriege ich von außen rein. Das heißt, bei der übergeordneten Komponente gibt mir die Highlighting-ID, also den Realtime-Push, den ich gerade bekommen habe. Und dann prüfe ich okay, ist es überhaupt meine ID? Und wenn ja, dann aktualisiere ich den VisitorsCount - und das ist das, was ich aktualisiere durch meinen Realtime-Push - die Anzahl der Teilnehmer.

Danach setze ich meine Highlight-Class und sage okay, jetzt darf natürlich gerendert werden, weil ich habe zum einen eine interne Variable geändert und ich habe mein Model geändert und das darf natürlich aktualisiert werden. Im anderen Fall prüfe ich natürlich: war ich das letzte Realtime? Also habe ich die Highlight-Class. Wenn nein, dann soll nicht gerendert werden. Wenn ja, dann darf natürlich gerendert werden, weil ich möchte das natürlich zurücksetzen, in diesem Fall mit "string.Empty". Das heißt, die Klasse wird wieder dem ganzen entzogen und das ganze wird gerendert. Ansonsten setzen wir ShouldRender auf "False" und die Komponente wird nicht gerendert. Jetzt haben wir das ganze hier auf "True" und wir schauen uns das ganze mal an, wie das mit "True" in der Debug-Konsole ausschaut und dafür drücke ich mal hier F12 und gehe in die Konsole und ihr seht hier schon: beim ersten Mal - ganz klar werden die ganzen Elemente gerendert, aber ich mache jetzt hier mal weg. Es kommt ein Relatime-Push-Event herein und was passiert? Alle 64 Komponenten werden gerendert, obwohl nur zwei Stück eigentlich wirklich davon betroffen sind.

Das ist zum einen die, die zuletzt die gehighlight war und die, die jetzt gehighlight wird. Jetzt schauen wir uns das Ganze mal an, wenn ich hier an das ShouldRender zurückgebe, dass heißt meine eigene Logik einbaue, wann ich möchte, dass die Komponente gerendert wird, abhängig von "FirstRender". Das ist natürlich klar. Und wir schauen uns das Ganze jetzt in der Konsole an: Wenn ein Highlight reinkommt, dann passiert was. Es wird genau das hier gerendert. Wir haben zum einen die, die gerendert wird. Wenn ich das Ganze wegnehme. Ich habe die, die gerendert wird, wenn ich das Ganze aktualisiere. Natürlich haben wir das Ganze jetzt zweimal, weil wir bekommen von außen einen Parameter rein und ich sag ShouldRender ist "True", daher haben wir jetzt hier den Fall, dass es zweimal gerendert wird. Aber nichtsdestotrotz haben wir schon deutlich weniger Renderings wie davor. Und das Ganze ist auch relativ performant, weil wenn ich jetzt hier runter scrolle, werden wir sehen: da passiert nicht viel, weil wir merken es eigentlich auch gar nicht, wenn jetzt hier was passiert mit dem Realtime. Das einzige, was natprlich hier passiert ist, dass hier das Rendering getriggert wird, weil wir natürlich das Highlighting bekommen und Parameter ändern und dann wird auch gerendert. Gut, das war jetzt mal soviel zu dem ShouldRender hier. Das Ganze kann man natürlich auch an anderen Stellen einsetzen, wie jetzt zum Beispiel hier in unserem Editor. Hier haben wir jetzt zum Beispiel den Fall, dass wir rendern. Ich habe jetzt hier mal CustomInput der Render by ShouldRender beinhaltet. Warum habe ich das so geschrieben? Das soll einfach darstellen diesen Input, bei dem ich das ShouldRender jetzt optimieren möchte und da können wir auch mal kurz reinschauen, da habe ich hier ein Formular mit CustomInput und hier habe ich zum Beispiel den HashCode - das wäre eine andere Möglichkeit, das Ganze zu definieren - indem ich mir einfach von der Value den HashCode oder zum Beispiel von meinem Parameter den HashCode hole, den abspeichere und dementsprechend dann vergleiche, ob sich der HashCode geändert hat. Und dann könnte ich auch hier und hier in der Textarea ähnlich das ShouldRender zurückgeben. Und wir werden sehen. Hier auch der Haken, wenn man das gerade gesehen hat, das Hot-Reload greift. Wenn wir jetzt hier reingehen, zack, passiert hier unten nichts, hier passiert nichts, weil nur das gerendert wird. Hier ist es ein anderer Fall, da komme ich gleich zu. Aber ihr seht, es wird immer nur das gerendert, was ich auch möchte, das gerendert wird. So können wir mit ShouldRender schon ziemlich viel machen und ziemlich viel verhindern in puncto Rendering. Weil man muss sich im Klaren sein, jede Komponente, die ich baue, hat ihren Lifecycle und jedes Mal, wenn was getriggert wird, wird dieser Lifecycle durchlaufen. Und je nachdem wie viel in der Komponente drinsteckt, kann das natürlich zu starken Performanceproblemen führen, wenn das jedes Mal gerendert wird.

Gut, dann gehen wir zurück in die Slides und schauen uns das nächste Thema an! Wir haben jetzt hier Events- Reaction. Events-Reaction - da geht es mehr um DOM-Events. Und da schauen wir uns jetzt noch mal den Render-Zyklus an. Hier haben wir ja vorhin gesehen, was passiert, wenn eine Komponente gerendert wird.

Jetzt ist die Frage: ok, ein DOM-Event passiert. Jeder kennt es aus der JavaScript-Welt "DOM-Events", wenn er schon einmal mit Webanwendungen gearbeitet hat: ein onInput, onClick, onBlur, onChange - was auch immer. Also sind sämtliche Inputs, die es gibt und jedes Mal, wenn das passiert, wird ein Event-Handler ausgeführt und das Rendern Component wird getriggert. Und das kann natürlich zu sämtlichen Renderings führen. Wenn man zum Beispiel ein Klick in einer Liste hat oder man klickt irgendwo einen Button, zum Beispiel einen Add-Button, dann wird ein Rerendering angetriggert. Also das kann schon auch zu massiven Problemen führen, weil hier standardmäßig einfach direkt durchgepusht wird. Und genau das wollen wir natürlich verhindern und möchten, dass das Ganze natürlich nicht ausgeführt wird. Was kann ich dafür tun? Oder was ist die Möglichkeit, was ich dagegen machen kann. Es gibt in der ComponentBase ein Interface, welches implementiert wird. Das ist das IHandleEvent. Das IHandleEvent ist genau das, was ausgeführt wird, wenn ein DOM-Event getriggert wird. Das heißt, hier wird die HandleEventAsync-Methode aufgerufen und diese ist natürlich ähnlich wie ShouldRender aufgebaut.

Microsoft hat sich gedacht: Ja, rendere durch - passt für mich. Dann haben wir das Ganze natürlich aktuell. Performance interessiert mich nicht, also wird gebaut. Daher die Optimierungsmöglichkeit: Ich überschreibe das IHandleEvent selbst und implementiere es selbst. Was natürlich ein bisschen aufwendiger ist. Ich zeige euch gleich, wie die Implementierung aussieht, zumindest die in der Basisklasse und wie man das finden können. Und das zweite, was jetzt so in die Richtung .NET 5 aufgenommen wurde, sind die EventUtils. Die EventUtils ist eine Helfer-Klasse, die von Microsoft angeboten wird, mit der es möglich ist, zu sagen, ok, ich möchte jetzt genau dieses Event oder dieses DOM-Event ausführen, ohne das etwas weitergegeben wird. Viele kennen das vielleicht mit PreventDefault aus den Events von JavaScript oder StopPropagation. Sowas in der Art machen die EventUtils in dem Fall. Diese sind aber leider noch nicht im Framework und ich habe es auch noch nicht gesehen, dass mit .NET 7 kommt. Aber sie haben es bei Microsoft auf Ihrer Webseite. Ich habe euch auch unten den Link dazu in die Slides gepackt, wo man sich die Klasse rauskopieren kann.

Gut, dann zurück zur Demo und wir schauen uns das Ganze mal an!

Lass mich mal kurz reingrätschen, Patrick. Ich würde mal für die, die jetzt schon sagen: ich erkenne da bestimmte Issues, die ich in meiner Blazor-Anwendung habe und mit dir darüber sprechen wollen, mal das Expert-1:1 ankündigen. Das haben wir eingangs angeteasert. Wenn ihr da also Fragen diskutieren möchtet, wo ihr euren Quellcode vielleicht auch kurz zeigen wollt oder ein bisschen länger beschreiben müsst, was euer Performance-Issue ist, könnt ihr euch kostenlos registrieren für diese Geschichte. Wir versuchen, weil meist viele Anfragen reinkommen, dass wir das an einem Vormittag irgendwie alles unterbekommen, die Slots also aufgehen. Da könnt ihr euch gerne dafür anmelden und ich und der Patrick melden uns bei euch mit einem Terminvorschlag. So könnt ihr das gerne in Anspruch nehmen.

Genau. Ja, genau. Wenn ihr dazu Fragen habt, könnt ihr beim Expert-1:1 gerne mit mir quatschen. Gar kein Problem. Jetzt kommen wir mal in die HandleEventInputText.Razor. Hier habe ich das IHandleEvent mal implementiert. Und ich zeige euch mal die Implementierung. Die ist etwas länger von HandleEventAsync. Da wird ein Task geholt, wir kriegen die Args und dann heißt es ShouldAwaitTask. Und dementsprechend habe ich hier schon mal was eingebaut. In der Basisimplementierung sieht das ganze so aus: StateHasChanged() und der Task wird dann dementsprechend weitergegeben. Das wollen wir eigentlich nicht. Mein StateHasChanged() führt natürlich ein Rerendering aus und das führt natürlich dazu, dass das Rendering ausgeführt wird. Und das ist etwas, dass wir gerne verhindern möchten oder bzw nicht jedes Mal haben möchten. Dafür habe ich jetzt eine Variable erstellt, ähnlich wie bei ShouldRender, die dann einfach sagt okay, preventRender. Das heißt, ich möchte, dass das preventRender durchgeführt wird oder nicht. Das prüfe ich hier ab.

Und zum Schluss setze ich das Ganze wieder zurück, weil wir prüfen das jedes Mal aufs Neue. Und dann gehen wir hier mal in den HTML-Part und schauen hier oninput habe ich hier registriert für meine CurrentValue und hab hier gesagt für preventRendering. Was mache ich hier drin? Ich gehe rein, setzt die CurrentValue und sagt preventRender "False". Das ist jetzt in dem Fall noch der Fall, das ist durchgeführt wird. Das schauen wir uns jetzt auch in unserer Demo an. Dazu gehe ich jetzt hier rein und fange hier mal an, zu triggern. Und ihr seht schon: jeder Click, den ich mache, führt ein Rendering aus. Das heißt sobald ich mich an DOM-Event hänge, wird bei jeder einzelnen Ausführung das ganze getriggert. Ich habe 84x die Komponente gerendert und ich frage mich warum. Ich habe eigentlich nur den Text getriggert. Also das ist nicht notwendig. Das heißt, wir können hier sagen okay, preventRender auf "True" beim onInput und dementsprechend wird dann hier nur gerendert, wenn ich aus der Komponente rausgehe. Wir haben oben noch ShouldRender drin. Das heißt, es wird hier nur dementsprechend weniger gerendert.

[...] Und auch hier wird nur einmal gerendert, sobald ich rausgehe. Hier habe ich es auch. Wenn ich jetzt zum Beispiel rausgehen und es hat sich nichts geändert und rendert es gar nichts. Also auch hier sind die Rendering-Times deutlich geringer. Ich schließe das Ganze mal, mach mal eine neue auf und gehe jetzt mal hier rein und dann gehe ich in das nächste und dann hat es hier noch einmal gerendert. Jetzt gehe ich wieder raus, zack ist das Ganze gerendert. Es sind schon deutlich weniger Render-Event. Das also können wir mit HandleEvent machen. Aber ich finde, HandleEvent ist schon ziemlich mächtig. Das sind jetzt schon allein von hier Zeile 39 bis Zeile 83 extrem viele Zeilen Code. Die kann man natürlich in eine Basisklasse packen. Also das man sagt: Ich packe das Ganze in eine Basisklasse und baue mir selbst meine eigene ComponentBase-Klasse auf, in der ich dementsprechend schon Logik vorgebe oder Logik anbiete, die das Rendering dementsprechend nach unten drückt und dementsprechend auch verhindert, dass man zum Beispiel sagt okay, ich mache hier einen Component-Hash, der immer geprüft wird und gebe in der Basisklasse die Möglichkeit, das Ganze zu überschreiben.

Wäre natürlich auch eine Möglichkeit. So hätte man das HandleEventAsync drin und man hätte ShouldRender drin und könnte dementsprechend das Ganze natürlich deutlich optimieren. Hier in unserem Sample als Beispiel meiner eigenen Komponente, wo ich das Ganze mal aufgeführt habe und wie man das Ganze optimieren kann. Gut, aber eine Sache habe ich vergessen. Jetzt gehe ich doch noch mal kurz zurück. Ich habe noch eine Kleinigkeit vergessen, denn wir schauen uns mal in der Konsole was an! Ich klicke hier drauf und auch ein Klick ist ein DOM-Event und ihr seht hier, es wurde wieder alles gerendert. Ich klicke jetzt auf Abbrechen, zack, es wird alles gerendert. Das ist natürlich relativ unschön, wenn ich nur bei einem Button-Click die ganze Liste rendere, weil auch da: es hat sich in dem Moment nichts geändert. Wenn ich von einer Page zurückkomme, ganz klar, das ist was anderes. Da kann sich was geändert haben. Aber hier jetzt in dem Fall müsste wenn dann nur das aktualisiert werden, was ich auch gerade bearbeitet habe. Was kann ich dagegen tun?

Und hier kommen jetzt die EventUtils zum Einsatz. Die EventUtils ist die Helper-Klasse, die ich angesprochen habe und das ist genau diese hier. Die EventUtils hat gerade mal 38 Zeilen und ist aber trotzdem ziemlich mächtig. Was bietet die EventUtils an? Eine Methode, die heißt AsNonRenderingEventHandler. Das kann unterschiedlich sein. Entweder ein Void-EventHandler als Action, mit Value. Es kann eine Function sein. Also egal was für ein EventHandler wir haben, wir können hier AsNonRenderingEventHandler nutzen und die machen nichts anderes als auch das IHandleEvent zu implementieren und den Invoke auszuführen. Das heißt auch hier wird der StateHasChanged nicht ausgeführt, aber das ganze natürlich deutlich schmaler. Und das Ganze ist auch sehr einfach einzusetzen, in dem wir dann sagen okay, schauen wir mal, wo wir das Ganze einsetzen können. Ich gehe jetzt mal in die Contributions und sehe okay, ich habe hier meinen ItemClicked und den möchte ich jetzt mal ändern, indem ich sage EventUtils.AsNonRenderingEventHandler. Und da gebe ich dann mal meine Action rein. Das wäre dann in dem Fall "CollectionItemClicked". Das packen wir hier rein. Und dann haben wir den EventUtils schon im Einsatz. Ihr seht, dass ist relativ einfach einzusetzen, indem ich einfach die Methode hier nutze. Meinen eigentlichen Code hier reinpacke, das heißt das ItemClicked und dementsprechend speichere ich das ganze mal und switche zum Browser rüber und wir gehen wieder in die Konsole und schauen, was passiert. Es wurde nur eins gerendert, nämlich das, wo ich drauf geklickt habe und ansonsten keins. Ich gehe jetzt zurück. Gehe noch einmal in die Konsole. Auch hier tut sich nichts mehr. Das Ganze funktioniert einwandfrei. Es wird wirklich nur ganz, ganz wenig gerendert. Stellt euch mal vor, wir wären noch in der Liste mit 1000 Elementen. Wir hätten den Trigger drin, dass beim onClick das Ganze gerendert wird.

Das wäre ziemlich lahm und das macht das ganze auch extrem unschön. Daher hier die Performance Optimierung. Einfach die EventUtils einsetzen und ihr habt dadurch schon extrem viel gewonnen.

Jetzt komme ich zum nächsten Punkt, wo natürlich auch der Widerspruch kommt. Was ist eigentlich, wenn ich jetzt doch mein Event brauche, weil ich jetzt zum Beispiel eine Suche habe oder ähnliches? Wie kann ich denn da was machen? Weil ich will ja, dass gerendert wird, weil ich will ja, dass meine Liste aktualisiert wird. Das vollkommen richtig. Und da gibt es jetzt die Möglichkeit zu sagen: okay, ich habe ja immer noch JavaScript zur Hand. Das heißt, ich könnte zum Beispiel mein Event debouncen, throtteln, slicen und was auch immer. Könnte ich ja dementsprechend auch noch tun. Und das kann ich euch jetzt auch zeigen anhand von einem Beispiel. Und da gehen wir mal in die Demo und schauen uns das Ganze hier an. Ich gehe vor allem mal hier in den Network Tab von den Developer Tools des Browser. [...] Und ihr seht schon, dass [...] mit jedem einzelnen Klick oder Tastendruck, den ich mache, wird die Suche getriggert und das ist auch ziemlich viel und es ist auch ziemlich viel Rendering, wie wir hier drin sehen. Also da wird schon extrem viel gerendert und auch das finde ich ziemlich unschön, weil ich kenne es aus eigener Erfahrung. Wenn ich jetzt die Taste B drücke, will ich nicht gleich meine Ergebnisse sehen. Das ist sehr viel zu schnell. Ich möchte erst mal ein bisschen was eintippen und dann zu sagen okay: Jetzt kannst du mal anfangen, zu suchen. Und genau so was habe ich gebaut und dafür gehe ich mal in die SearchBar. Und die SearchBar ist einfach ein Input. [...].

Und jetzt haben wir hier die SearchBar mit einer Reference. Das erkläre ich gleich. Dann haben wir hier die Class. Ich binde meinen SearchTerm und ich habe hier ein Placeholder drin. Das ist mein Input. Und ich mache hier ein Two-Way-Binding auf den SearchTerm. Und hier mein SearchBar-Element. Und jetzt sehe ich hier. Okay, Ich habe hier eine Referenz. Und ich habe hier mein SearchBar-Element. Das heißt, wir können hier mit "@ref" eine Referenz im Code-Behind schaffen, auf meine Element-Reference. Das heißt, ich hab hier jetzt eine Referenz auf meinen Input, den ich gleich via JS-Interop an das JavaScript weitergeben kann. Dann habe ich hier eine selfReference von der Komponente, die ich auch an das JavaScript weitergeben möchte. Und ich habe mein Modul. Das kann zum Beispiel mit .NET 6 [...] von meiner Komponente laden. Dass heißt, das JavaScript-File hängt wie das C#-File oder CSS-File an meiner Komponente dran und ich kann es dementsprechend dann in mein Modul laden und kann dann dementsprechend auch hier Methoden aufrufen.

Ich habe jetzt hier zum Beispiel schon ein Intervall auf Null gesetzt gehabt, weil ich das Ganze natürlich aus Zeitgründen für das Webinar schon vorbereitet habe. Aber ich sage es hier mal ich möchte jetzt hier 500 Millisekunden debouncen und rufe hier auch die Debounce-Methode auf. Wie sieht diese Methode aus? Das ist eine Export-Function, dass heißt alles was die Methode exportiert, kann ich aus .NET aufrufen. Alles was eine normale Funktion ist, nicht. OnDebounceInput nimmt dann das Element, die Komponente und den Intervall, das heißt meine Input-Komponente, die Referenz auf meine SearchBar-Komponente und den Intervall. Für was genau brauche ich jetzt die einzelnen Dinge. Zum einen habe ich den EventListener auf mein Element, dass heißt auf dem Input registriere ich mich auf das Event "input" sage dann: okay, ich möchte debounce. Das ist eine JavaScript-Methode und wenn der Intervall durch ist - dass heißt, ich habe seit einer halben Sekunde nichts mehr getippt - wird auf meiner Komponente die Methode "HandleOnInput" aufgerufen. Dass heißt, hier brauche ich meine Komponente. Da ich dadurch die .NET-Methode aufrufen kann und ich übergebe den aktuellen Wert. Dann gehen wir in den C#-Code zurück und sehen hier okay, hier ist die JSInvokable-Methode, HandleOnInput, da bekomme ich meinen Value rein und wenn dieser sich geändert hat, ändere ich den ganzen und gebe ihn nach außen weiter.

Gut, jetzt haben wir das ganze auf 500 Millisekunden geändert und jetzt schauen wir uns das ganze mal an, indem ich das wieder zurücksetze, wie oft jetzt im Network-Tab vom Browser das Ganze getriggert wird. [...] Eigentlich sollte die Applikation es nach 500 Millisekunden nach einem Tastendruck abfeuern. Ja, genau. [...] Jetzt wird erst, wenn ich aufgehört habe, zu schreiben, der Request gesendet. [...]

Gut, aber so viel zu DOM-Event, die wir verhindern möchten. Das heißt, was wollte ich euch damit zeigen? Wir haben immer noch die Macht von JavaScript. JavaScript ist ja bei Blazor WebAssembly nicht weg. Das heißt, wenn Blazor WebAssembly mit seinen Features und Möglichkeiten noch nicht so weit ist, um zum Beispiel jetzt das hier zu verhindern, hat man immer noch die Möglichkeit auf das JavaScript zurückzugreifen und das JS Interop zu nutzen und dadurch vielleicht das eine oder andere noch zu optimieren oder zu verbessern.

Heißt das im Umkehrschluss auch Patrick, wenn Leute mit .NET-Hintergrund Blazor lernen und da sagen "Mit Blazor brauche ich kein JavaScript mehr", dass das also nicht 100 % wahr ist? Man sollte JavaScript durchaus können, um dann an diesen Edge-Cases weiter zu kommen?

Ja, also Blazor WebAssembly heißt nicht gleichzeitig ich muss kein JavaScript mehr können. Nein, das heißt nicht! Wir haben sowohl HTML, CSS als auch JavaScript. Natürlich ist der JavaScript-Teil bei Blazor WebAssembly deutlich minimiert. Also dieser Teil ist lang nicht so groß, wie wenn ich jetzt mit einem JavaScript-Framework arbeiten würde. Aber wir haben immer noch Grenzfälle oder Fälle, wo JavaScript natürlich zum Einsatz kommt. Und da muss JavaScript auch eingesetzt werden. Ist aber im Umfang natürlich deutlich geringer, aber nicht komplett weg. Also de facto "Nur noch C# - das ist hier bei Blazor WebAssembly nicht der Fall".

Gut. Somit komme ich auch zu meinem Resümee.[...] Was haben wir heute so ein bisschen mitnehmen können? Listen optimieren durch den Einsatz der Virtualize-Component. Ganz, ganz großer Performance-Schub, weil die Komponente bringt so viel mit mit der Virtualisierung, mit dem Lazy Loading, mit dem Placeholder. Und das Ganze ist wirklich performant. Wir können es selbst stylen, dass heißt, es ist keine 3rd-Party-Komponente, wo ich gucken muss, wie kriege ich das jetzt irgendwie gestylt? Nein, das ist komplett style free, das heißt können wir unseren eigenen Style draufpacken. Und das Ganze funktioniert immer noch.

Dann muss ich immer mal wieder darüber nachdenken, muss ich meine Komponente rendern? Ist es wirklich notwendig oder kann ich vielleicht irgendwas tun oder prüfen, wo ich sage, hier bei dem Parameter brauche ich jetzt nicht rendern, außer es ist wirklich zwingend notwendig. Das prüfe ich aber selbst. Da immer mal wieder darauf achten. Auch bei der IHandleEvent-Methode immer mal wieder darauf achten, wann wird meine Komponente gerendert? Weil manchmal hat man einfach ein Event registriert, hat es gar nicht im Hinterkopf, dass jedes Mal, wenn das getriggert wird, auch ein Rendering durchgeführt wird von der Komponente. Und das ist natürlich auch schnell ein Performance-Problem. Auch hier immer wieder mal darauf achten und wir haben ja die Möglichkeit, da das ganze offen gelegt ist, das Interface zu limitieren und selbst die Hoheit darüber zu bekommen.

Und dann natürlich, wenn es nicht immer rerendert werden soll, verhindern. Wenn es getriggert werden soll, haben wir die Möglichkeit, das ganze ja irgendwie zu verhindern. Vielleicht auch im .NET-Code zu sagen, dass wir hier ein Timing reinbringt und es dementsprechend dann erst rendert, wenn es soweit notwendig ist. Mit JavaScript es natürlich leicht möglich, da wir ja die Brücke mit JS-Interop haben. Auch hier haben wir die Möglichkeit Performance zu optimieren. Und ich finde, schon anhand dieses leichten, kleinen Beispiels, das wir jetzt hatten - es war nur eine Listendarstellung mit einem kleinen Editor und einer Suche - konnte man schon sehen, was kleine Stellschrauben bewirken können, um bei so einer kleinen Webseite die Performance deutlich zu steigern.

Und somit bin ich auch am Ende. Ich hoffe, ich konnte euch ein bisschen was Neues zeigen, ein bisschen aufmerksam auf den einen oder anderen Punkt machen. Und ich hoffe, ihr hattet auch Spaß daran und konntet sehen, dass Blazor WebAssembly schon ziemlich cool ist. Und mir macht es immer wieder Spaß, mit Blazor zu arbeiten und ich hoffe, das kommt bei euch auch soweit rüber und an und dass ihr auch weiterhin Spaß mit Blazor habt. Von daher vielen Dank für eure Aufmerksamkeit und hoffentlich bis bald.

Vielen Dank Patrick. Für alle, die vielleicht noch weiter lesen möchten und noch mehr zu Blazor wissen möchten. Ich blende euch mal den Link zu unserer Landingpage zu Blazor ein. Da haben wir nämlich alles gesammelt, was wir so in den vergangenen Monaten zu Blazor publiziert haben: Artikel, Videos, Code Snippets und so. Schaut da gerne mal rein. Da sind viele von diesen Betrachtungen, die jetzt der Patrick im Bereich Performance gezeigt haben und einiges mehr abgebildet. Könnt ihr stöbern. Ist alles free. Alles kostenlos verfügbar. Und dann könnt ihr dort schauen, ob ihr noch den einen oder anderen Tipp findet, der für vielleicht für euch, für euer Projekt hilfreich ist.

Dann würde ich sagen, gehen wir mal in die Fragen. Dazu muss ich kurz mal die Datei, die ich schon vorbereitet habe, öffnen. Weil Patrick, ich lasse dich so einfach nicht davon. [...] Und da freue ich mich, dass der Peter so aktiv war.

Ok, die Frage, die kenne ich sogar schon. Wird beim Rendern in Blazor WASM der virtuelle oder reale Dom gerendert? Oder beide? Bei Blazor WebAssembly ist es so, das ist der reale DOM - den virtuellen DOM, den gibt es so gar nicht. Also bei Blazor WebAssembly wird immer der reale Dom gerendert. Also das kann ich schlicht und einfach beantworten. Da ist virtual DOM nicht vorhanden und wird dementsprechend auch nicht aktualisiert, sondern das Triggern von JavaScript bzw. von dem JS-Interop, dass das Update des DOMs triggert, aktualisiert den realen DOM.

Ok. Und dann hat der Peter noch das hier gefragt. Das ist schon sehr, sehr speziell, habe ich so gedacht. Weiß ich gar nicht ...

Das geht schon in Richtung State Management, MVVM. Klassischer Ansatz, den ich aus meinen alten Tagen [...] kenne: OnPropertyChanged mit jeweils StateHasChanged aufrufen. Okay, natürlich, jedes StageHasChanged heißt auch jedes Mal wird ein neues Triggering ausgeführt. Wenn nicht 3, 4 oder 5 habe, dann werden 3, 4, 5 StateHasChanged ausgeführt.

Liegen die dann nacheinander in einer Art Queue oder feuert das System wirklich parallel und sagt, ja jetzt wird es teuer?

Da wir hier kein Multi-Threading haben, wird das ganze nicht parallel ausgeführt, sondern nacheinander. Multi-Threading ist auch erst in .NET 7 geplant gewesen, kommt aber wahrscheinlich erst mit .NET 8, dass heißt hier wird nacheinander das StateHasChanged gefeuert und dementsprechend wird dann auch das Rendering nach und nach ausgeführt.

Jetzt ist die Frage, ob das gut oder schlecht für den Peter in der jetzigen Situation ist, weil mit großer Kraft kommt auch viel Verantwortung, war das nicht so der Spruch von Spiderman? Wenn es dann erst mal alles parallel geht und dann die Post abgeht, muss man sich wieder selbst darum kümmern. So möglicherweise Sachen dann wieder schon abbricht, damit man sich die Seite nicht überlastet.

Genau deswegen - es ist schwierig bei jeder einzelnen Propery den StateHasChanged zu feuern. Feuert der Trigger zum Beispiel OnPropertyChanged. Vielleicht kann man hier auch eingreifen - kann sagen okay, das und das hat sich jetzt geändert. Ich möchte, dass jetzt gerendert wird. Vielleicht ist hier einfach die Möglichkeit zu sagen, baue ein ShouldRender ein, prüfe deiner Komponente, was sich geändert hat und dementsprechend render erst dann, wenn es auch wirklich notwendig ist. Oder du sagst, ich packe das ganze in ein Model und ich triggere erst dann, wenn sich das Model geändert hat das StateHasChanged. Da gibt es unterschiedliche Möglichkeiten, wie wir das Ganze oder wie das ganze optimiert werden könnte. Aber ja, ich kenne den MVVM-Ansatz. Deshalb bei jeder Property OnPropertyChanged das StateHasChanged getriggert wird. Und ja, leider ist es so, dass es aktuell nacheinander ausgeführt wird.

Okay. Peter gewinnt heute den Award für die meisten Fragen. Er hat nämlich, als ich schon die Fragen hier vorbereitet hatte, noch einen nachgeschoben. Deswegen habe ich dafür leider keine Slide und er wollte wissen, was Gründe sein können, dass nach einem Aufruf von StateHasChanged kein Rendern erfolgt.

Das ist sehr spannend. Das müsste man sich aber beim Aufbau der Anwendung anschauen, weil ich das so aus dem Stehgreif nicht sagen kann. Also es kommt immer darauf an, wie ist die Komponente aufgebaut? Wie ist das Ganze zusammengefügt worden? Was hier genau das Problem sein könnte, müsste man sich dann im Tieferen anschauen. Da kann ich dir das Expert-1:1 ans Herz legen, vielleicht das wir da mal zusammen draufschauen, um zu schauen, ob man da vielleicht finden, warum das passiert. Aber jetzt aus dem Stehgreif zu sagen okay, warum triggert StateHasChanged jetzt nicht das Rerendering, dass kann ich leider nicht sagen.

Es wäre also anders gedacht?

Genau. Eigentlich müsste die StateHasChanged dafür da, das Ganze dann neu zu rendern.

Alles klar. Damit hätten wir in der Tat alle Fragen beantwortet. Vielen Dank, Peter, dass du so aktiv dabei warst. Und hoffentlich registriert du dich für so ein Expert-1:1 und dann kannst du gerne den Patrick mit noch weiteren Fragen quälen. Allen anderen, hoffe ich, konnten wir einen guten Input geben, worauf man achten sollte, wenn man Blazor WebAssembly Applikationen programmiert, wie viel Power und Performance man gewinnen kann. Ich meine, man hat gesehen, es sind immer fachliche Entscheidungen, wo ich sage, hier möchte ich das Rendern abbrechen, hier brauche ich es nicht und so, deswegen verstehe ich natürlich auch, dass Microsoft den Ansatz, so wie sie ihn gewählt haben, gewählt hat und sagt Lieber mal ein bisschen zu viel rendern. Abschalten kann man es immer noch, umgekehrt sehr viel schwieriger. Nehmt aber diese Gedanken mit. Und dann habt ihr die passenden Rezepte an der Hand, um in eurem Projekt performant Blazor WebAssembly zu programmieren. Damit auch von meiner Seite: Vielen Dank und ich wünsche euch einen schönen Tag. Wir sehen uns hoffentlich beim nächsten Webinar von Thinktecture!.

Sie wünschen sich Unterstützung durch unsere Experten in Ihrem Projekt?

Slidedeck zu "Blazor WebAssembly: Performance-Optimierungen fürs UI"

Kommende Webinare: jetzt kostenlos anmelden

@ngrx/signals: Reaktives State-Management auf Komponentenebene

Yannick Baron | 03.04.2024 | 10:30 Uhr

Transparente AI: Tracing- und Debugging-Techniken für LLM-Anwendungen

Marco Frodl | 15.05.2024 | 10:30 Uhr

Hallo, .NET Developer!

Du begeisterst Dich für .NET-basierte Cloud-Native-Lösungen?

Du möchtest, dass Deine Expertise zum Einsatz kommt und von einer Expertenschaft gechallenged wird? Du weißt, dass nur durch Research auch während der Arbeitszeit neue Technologien erlernbar sind und erst damit Innovation möglich wird?
Erfahre hier mehr über uns als Team und was Dich bei uns erwartet.

Weitere Artikel zu Blazor, Blazor WebAssembly, Performance, UX/UI, WebAssembly

Blazor
favicon

gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

Wie in allen anderen browserbasierten Single-Page-Application (SPA) Frameworks, ist Blazor WebAssembly JSON-over-HTTP (über Web- oder REST-APIs) die bei weitem häufigste Methode, um Daten auszutauschen und serverseitige Vorgänge auszulösen. Der Client sendet eine HTTP-Anfrage mit JSON-Daten an eine URL, mitunter über unterschiedliche HTTP-Verben. Anschließend führt der Server eine Operation aus und antwortet mit einem HTTP-Statuscode und den resultierenden JSON-Daten. Warum sollte man das ändern? Nun, es gibt Gründe - vor allem wenn man in einem geschlossenen System ist und .NET sowohl im Frontend als auch im Backend einsetzt.
30.03.2023
Blazor
favicon

Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Stockende UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld - dies sind nur wenige Beispiele alltäglicher Probleme, die der Nutzung von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen, immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.
29.03.2023
Blazor
sg

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.
07.03.2023
.NET
cl-neu

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.
28.02.2023
Blazor
cl-neu

Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

ASP.NET Core Blazor is Microsoft's framework for implementing web-based applications, aimed at developers with knowledge of .NET and C#. It exists alongside other frameworks such as ASP.NET Core MVC. About two and a half years after the release of Blazor WebAssembly and based on our experiences from many customer projects at Thinktecture, we want to have a close look at the following questions: What is the current state of the framework? How can you successfully use Blazor? And where does it have limitations?
24.11.2022
Blazor
favicon

Blazor WebAssembly: Debugging gRPC-Web with Custom Chrome Developer Tools

If you are working with Blazor, gRPC is a big issue for transferring data from APIs to clients. One issue of developing with gRPC-Web is debugging the transmitted data because the data is in an efficient binary message format. In this article, I will show you how to solve this problem with the help of my NuGet.
17.11.2022

Unsere Webinare

Unsere Artikel

Mehr über uns