Frontend, Blazor, routing, state en componentopbouw
24.1 Doel en positie binnen het Technisch Ontwerp
Dit hoofdstuk beschrijft de technische opbouw van de Blazor-frontend van OefenHub. De frontend vormt de gebruikersgerichte applicatieschil, verzorgt routing, layouts, componentcompositie, formulieren, validatiefoutweergave, loading states, lege toestanden en presentatie van rolgebonden schermen.
Voor de interactieve Blazor Web UI gebruikt OefenHub MudBlazor als primaire componentenbibliotheek. Deze keuze betekent niet dat schermen als losse MudBlazor- of HTML-pagina's worden opgebouwd. De implementatie gebruikt MudBlazor achter een eigen OefenHub-componentenlaag met herbruikbare wrappercomponenten, themetokens, layoutpatronen en testbare componentcontracten.
De frontend is nadrukkelijk geen eigenaar van autorisatie, domeinregels, datatoegang of bronmutaties. De UI mag informatie tonen, acties starten en viewmodels samenstellen, maar de uiteindelijke toegangscontrole, validatie en opslag blijven server-side belegd bij de betreffende modulecontracten en domeinservices.
De eerste technische baseline richt de gebruikersinteractie in via de Blazor-webapp. Er wordt geen publieke functionele API-laag voor externe clients of mobiele apps ontworpen. Desktop en tablet zijn leidend voor schermopbouw, interactiedichtheid en oefenervaring; smartphonegebruik mag technisch niet bewust worden geblokkeerd wanneer responsive basisgedrag dit toestaat, maar is geen primair ondersteund of geoptimaliseerd scenario.
Belangrijke inputbronnen zijn:
- Functioneel Ontwerp: applicatieschil, header, footer en navigatie
- Functioneel Ontwerp: leerling oefenen, voortgang en resultaten
- Functioneel Ontwerp: berichten, communicatie en notificaties
- Database-informatie: identiteit en autorisatie
- Software Requirements Specification: niet-functionele requirements
24.2 Begrippen: Razor-componenten versus Razor Pages
OefenHub gebruikt Blazor/Razor-componenten voor de webinterface. In dit hoofdstuk wordt met een page of pagina een routeerbare Blazor-component bedoeld. Dat is niet hetzelfde als het klassieke ASP.NET Core Razor Pages-model met .cshtml-pagina's als primair applicatiepatroon.
Klassieke Razor Pages worden in de eerste technische baseline niet als hoofdstructuur voor functionele schermen gebruikt. Als in een toekomstige uitbreiding een technische reden ontstaat om voor een zeer beperkte infrastructuurpagina of fallbackroute toch een Razor Page toe te voegen, moet die keuze expliciet worden vastgelegd en mag dat geen tweede frontendarchitectuur naast Blazor introduceren.
| Begrip | Betekenis binnen OefenHub |
|---|---|
| Razor-component | Herbruikbare Blazor-component voor UI, layout of interactie. |
| Routeerbare component | Blazor-component met eigen route en paginafunctie. |
| Page | Functionele pagina in de Blazor-applicatie, technisch een routeerbare component. |
| Razor Page | Klassiek ASP.NET Core .cshtml-patroon; niet het standaardpatroon voor OefenHub-schermen. |
24.3 Basisprincipes
Voor de frontend gelden de volgende ontwerpprincipes.
| Principe | Regel |
|---|---|
| Server-side autorisatie is leidend | Routeparameters, zichtbare knoppen, browsergeschiedenis, clientstate en componentstate mogen nooit toegang afdwingen of verruimen. |
| Webapp is primair kanaal | OefenHub optimaliseert de interactie voor de Blazor-webapp; publieke externe clients of mobiele apps vallen buiten de eerste baseline. |
| Desktop en tablet leidend | Layouts, navigatie en oefeninteractie worden primair voor desktop en tablet ontworpen; smartphoneweergave is best-effort en geen aparte acceptatiebasis. |
| UI is compositie, geen domeinlaag | OefenHub.Web bevat geen domeinlogica, geen EF Core DbContexts en geen directe toegang tot module-interne entities. |
| Modulecontracten zijn de ingang | De UI roept publieke contracten, command-services, query-services en readers van modules aan. |
| Viewmodels zijn UI-specifiek | Web-viewmodels zijn afgestemd op schermopbouw en mogen geen database-entiteiten vervangen of lekken. |
| State is tijdelijk tenzij expliciet toegestaan | UI-state is standaard tijdelijk en wordt alleen persistent wanneer het functioneel en privacytechnisch is toegestaan. |
| Responsiviteit is presentatiegedrag | Een compacte header of menu-indeling verandert geen autorisatie, rolcontext of zichtbare dataset. |
| Foutafhandeling is veilig | Technische details, identifiers en payloads worden niet aan eindgebruikers getoond. |
| Toegankelijkheid is vanaf de eerste render relevant | Toegankelijkheidskeuzes mogen vóór login technisch worden toegepast, maar bevatten geen persoonsgegevens of autorisatie-informatie. |
| MudBlazor is componentbasis, niet de applicatiestijl | MudBlazor levert technische UI-primitives; OefenHub bepaalt de visuele identiteit via eigen thema, CSS en wrappercomponenten. |
| Herbruikbaarheid gaat vóór pagina-opbouw | Terugkerende UI-patronen worden als herbruikbare componenten gebouwd; pagina's componeren die componenten en bevatten geen gekopieerde mockupstructuren. |
| HTML-mockups zijn geen implementatiebasis | Mockups helpen schermopbouw, navigatie en verwachtingen toetsen, maar productie-UI wordt opnieuw opgebouwd vanuit de OefenHub-componentenlaag. |
24.4 Projectverantwoordelijkheid van OefenHub.Web
OefenHub.Web is het enige project dat de Blazor-applicatie host en gebruikersinteractie afhandelt. Het project is verantwoordelijk voor:
- routing en routecompositie;
- layouts en applicatieschil;
- header, footer en navigatie;
- routeerbare pagina's;
- herbruikbare componenten;
- MudBlazor-registratie, MudBlazor-themeconfiguratie en OefenHub-wrappercomponenten;
- formulieren en client-side validatiefeedback;
- viewmodels voor schermweergave;
- page composition over meerdere modulequery's;
- browsergerichte state;
- toepassing van toegankelijkheidsvoorkeuren;
- veilige loading-, empty- en error states;
- SignalR-clientintegratie voor realtime weergave;
- aanroepen van publieke modulecontracten.
OefenHub.Web is niet verantwoordelijk voor:
- EF Core datatoegang;
- migrations;
- database-entiteiten;
- businessregels;
- module-interne services;
- directe autorisatiebeslissingen;
- het bepalen van bron-van-waarheid voor resultaten, berichten, meldingen of relaties;
- het bewaren van server-side domeintoestand in browserstorage;
- het kopiëren van HTML-, CSS- of JavaScriptstructuren uit mockups als productie-implementatie.
24.5 Voorgestelde mappenstructuur van OefenHub.Web
De volgende structuur geldt als technische baseline voor het webproject. Mappen worden pas aangemaakt wanneer daar daadwerkelijk inhoud voor bestaat; de structuur is bedoeld als ordeningskader en niet als verplicht lege mapset.
OefenHub.Web/
Components/
Shared/
Shell/
Feedback/
DataDisplay/
Forms/
Student/
Teacher/
Guardian/
Admin/
Messages/
Tickets/
Practice/
Catalog/
Pages/
Public/
Account/
Profile/
Accessibility/
Preferences/
Student/
Teacher/
Guardian/
Admin/
Messages/
Tickets/
Practice/
Relationships/
Layouts/
Navigation/
State/
Forms/
Validation/
Theming/
ViewModels/
PageComposition/
Extensions/
wwwroot/
24.5.1 Betekenis per map
| Map | Inhoud |
|---|---|
Components | Herbruikbare UI-componenten zonder eigen hoofdroute; generieke OefenHub-wrappercomponenten staan bij voorkeur onder Shared, Shell, Feedback, DataDisplay of Forms. |
Pages | Routeerbare pagina's, gegroepeerd per rol of functioneel domein. |
Layouts | Applicatielayouts zoals publieke layout, ingelogde layout en oefenlayout. |
Navigation | Menu-opbouw, navigatiemodellen en presentatie van rolgebonden menu's. |
State | UI-statecontainers en browserstate-adapters zonder autoriserende waarde. |
Forms | Formuliermodellen en formuliergerichte componenten. |
Validation | Client-side validatiehulpen en mapping van servervalidatie naar UI. |
Theming | MudBlazor-themeconfiguratie, OefenHub-designtokens en gedeelde stylingafspraken voor het webproject. |
ViewModels | Schermspecifieke viewmodels voor rendering. |
PageComposition | Samenstelling van pagina-viewmodels uit meerdere modulequery's. |
Extensions | Registratie- en configuratie-extensies voor het webproject. |
wwwroot | Statische assets, CSS, JavaScript-hulpen en publieke webbestanden. |
24.6 Components
Components bevat herbruikbare UI-bouwstenen. Een component mag lokale interactiestate hebben, maar mag geen domeinmutatie uitvoeren zonder expliciet aangeroepen modulecontract.
Voorbeelden:
Components/Shared/LoadingPanel.razor
Components/Shared/EmptyState.razor
Components/Shared/ConfirmDialog.razor
Components/Shared/PagedTable.razor
Components/Practice/ExerciseQuestionPanel.razor
Components/Practice/ExerciseProgressBar.razor
Components/Messages/UnreadBadge.razor
Components/Tickets/TicketStatusBadge.razor
Componenten worden bij voorkeur klein gehouden. Wanneer een component veel domeincontext nodig heeft, hoort de dataopbouw in PageComposition of in een pagina, niet in een diep geneste UI-component.
24.6.1 Componentregels
| Regel | Toelichting |
|---|---|
| Componenten accepteren parameters | Data wordt via parameters of viewmodels aangeleverd. |
| Componenten lekken geen entities | Database-entiteiten worden niet als componentparameter gebruikt. |
| Componenten voeren geen verborgen autorisatie uit | Autorisatie gebeurt server-side via modulecontracten. |
| Componenten mogen events teruggeven | Bijvoorbeeld OnSave, OnCancel, OnSelected. |
| Componenten mogen loading en empty states tonen | De betekenis van de lege toestand komt uit de aanroepende pagina/viewmodel. |
24.6.2 MudBlazor als gecontroleerde componentbasis
MudBlazor is de primaire componentenbibliotheek voor de interactieve Blazor Web UI. OefenHub gebruikt MudBlazor voor generieke UI-primitives zoals knoppen, invoervelden, selecties, tabs, dialogs, overlays, kaarten, menu's, snackbars, tooltips, progress-indicatoren, chips, badges en tabulaire weergaven.
De keuze voor MudBlazor is technisch begrensd:
- MudBlazor wordt alleen gebruikt binnen
OefenHub.Weben eventueel webspecifieke testprojecten. - Domeinmodules, applicatieservices, databaseprojecten, oefenmodulecontracten en readmodelcontracten krijgen geen afhankelijkheid op MudBlazor.
- MudBlazor-types worden niet gebruikt in publieke modulecontracten, command/query-contracten, readmodels of domeinmodellen.
- MudBlazor bepaalt niet welke acties zijn toegestaan, welke data zichtbaar is of welke businessregel geldt.
- Server-side modulecontracten blijven bron voor autorisatie, validatie, mutaties en dataselectie.
- MudBlazor is geen bron van functionele requirements en vervangt geen schermdocumentatie, Functioneel Ontwerp of Software Requirements Specification.
MudBlazor mag rechtstreeks in een routeerbare pagina worden gebruikt wanneer het gaat om eenvoudige, eenmalige compositie zonder herbruikbaar patroon. Zodra een UI-patroon terugkeert, roloverschrijdend is of eigen gedrag heeft, wordt het patroon als OefenHub-component of wrappercomponent geïsoleerd.
24.6.3 OefenHub-componentenlaag bovenop MudBlazor
OefenHub krijgt een eigen componentenlaag bovenop MudBlazor. Deze laag bevat herbruikbare componenten, layoutpatronen, themetoepassing en uniforme interactieafspraken. Pagina's gebruiken bij voorkeur deze OefenHub-componenten in plaats van losse combinaties van MudBlazor-componenten en pagina-eigen HTML.
Voorbeelden van herbruikbare componenten en patronen:
| Component/patroon | Doel | Voorbeelden van gebruik |
|---|---|---|
OefenHubPageHeader | Titel, context, primaire acties en secundaire navigatie. | Frontpages, beheerpagina's, detailpagina's. |
OefenHubCard | Standaard kaartweergave met titel, inhoud, metadata en acties. | Dashboardtegels, leerlingkaarten, modulekaarten. |
OefenHubActionBar | Consistente plaatsing van primaire, secundaire en destructieve acties. | Opslaan, annuleren, exporteren, terugkeren. |
OefenHubFilterBar | Filters, zoekveld, sortering en resetactie. | Geschiedenis, tickets, accounts, modules. |
OefenHubDataList / OefenHubDataTable | Lijst- of tabelweergave met lege toestand, loading state en paginering. | Accounts, meldingen, resultaten, relaties. |
OefenHubStatusBadge | Uniforme statusweergave zonder domeinlogica. | Ticketstatus, online-status, modulevrijgave, runstatus. |
OefenHubEmptyState | Consistente lege toestand met uitleg en optionele actie. | Geen berichten, geen kinderen, geen resultaten. |
OefenHubLoadingPanel | Consistente laadweergave en skeleton/loadinggedrag. | Pagina's, kaarten, tabellen, modals. |
OefenHubConfirmDialog | Bevestiging voor risicovolle of definitieve acties. | Verwijderen, ontkoppelen, overdragen, anonimiseren. |
OefenHubFormSection | Herbruikbare formuliersectie met label-, hint- en validatiestructuur. | Profiel, voorkeuren, beheerinstellingen. |
OefenHubMessagePanel | Veilige melding-, waarschuwing- en foutweergave. | Servervalidatie, access denied, fallbackgedrag. |
OefenHubLiveIndicator | Consistente realtime statusweergave zonder brondata te dupliceren. | Online-overzicht, live meekijken. |
De concrete namen mogen tijdens implementatie worden verfijnd, maar het principe is verplicht: terugkerende UI wordt niet per pagina opnieuw gebouwd.
24.6.4 Hergebruikregels
Voor hergebruik gelden de volgende regels:
| Regel | Toelichting |
|---|---|
| Twee keer is componentkandidaat | Komt dezelfde opbouw of interactie op twee schermen terug, dan wordt een herbruikbare component overwogen. |
| Roloverschrijdend is shared | Patronen die bij leerling, docent, ouder/voogd en beheerder terugkomen, horen in Components/Shared of een generieke submap. |
| Domeinspecifiek gedrag blijft gescheiden | Een algemene kaart mag geen docent-, leerling- of supportbusinessregels bevatten. Domeinspecifieke varianten gebruiken parameters of aparte domeincomponenten. |
| Pagina's componeren | Routeerbare pagina's halen data op via page composition en combineren componenten; zij bevatten zo min mogelijk visuele detailopbouw. |
| Styling is token- of themegedreven | Kleuren, spacing, radius, typografie en states worden centraal beheerd via theme/CSS-variabelen, niet per pagina hardcoded. |
| Wrapper vóór herhaling | Terugkerende combinaties van MudBlazor-componenten worden als OefenHub-wrapper vastgelegd voordat zij op meerdere plaatsen worden gekopieerd. |
| Oefenmodules blijven vrij binnen contract | Concrete oefenmodule-rendering mag eigen interactieve componenten hebben, maar gebruikt gedeelde shell-, feedback-, status- en formuliercomponenten waar passend. |
24.6.5 Gebruik van HTML-mockups
De bestaande HTML-mockups zijn ontwerp- en afstemmingsartefacten. Zij mogen worden gebruikt om schermopbouw, navigatie, inhoudsvolgorde, zichtbare labels, informatiestructuur, lege toestanden en interactie-intentie te begrijpen.
De HTML-mockups zijn geen technische implementatiebasis. Voor implementatie gelden daarom expliciet de volgende verboden:
- mockup-HTML kopiëren naar
.razor-pagina's als productiestructuur; - mockup-CSS kopiëren als pagina-eigen stylinglaag zonder vertaling naar OefenHub-theme of componentstijl;
- mockup-JavaScript gebruiken als interactielogica in de Blazor-applicatie;
- per scherm een zelfstandig opgebouwde componentstructuur maken wanneer hetzelfde patroon al elders voorkomt;
- visuele overeenkomst boven componenthergebruik, toegankelijkheid, autorisatiegrenzen of testbaarheid plaatsen.
De juiste werkwijze is:
- bepaal uit schermdocumentatie en mockup welke functie, flow en visuele intentie nodig zijn;
- controleer of een bestaand OefenHub-componentpatroon dit dekt;
- breid waar nodig de componentencatalogus uit;
- bouw de pagina opnieuw op met Blazor, MudBlazor en OefenHub-wrappercomponenten;
- vergelijk het resultaat visueel en functioneel met schermdocumentatie en mockup;
- leg afwijkingen terug in schermdocumentatie, Functioneel Ontwerp of Technisch Ontwerp wanneer zij inhoudelijk relevant zijn.
24.6.6 Theming en visuele identiteit
De OefenHub-visuele identiteit wordt centraal ingericht. MudBlazor levert de componentbasis, maar niet automatisch de uiteindelijke stijl.
Minimale afspraken:
- er komt één centrale MudBlazor-themeconfiguratie voor kleuren, typografie, spacing, radius, elevation en states;
- OefenHub-specifieke CSS-variabelen of tokens worden gebruikt voor merkstijl, rolaccenten en kindvriendelijke visuele accenten;
- pagina's gebruiken geen willekeurige inline styling voor kleuren, spacing of typografie;
- toegankelijkheidsvarianten zoals contrast, lettergrootte en dyslexievriendelijke weergave worden via centrale theming of gecontroleerde CSS-classes toegepast;
- publieke pagina's, ingelogde app, beheeromgeving en oefencontext mogen eigen layoutaccenten hebben, maar delen dezelfde technische componentbasis.
24.6.7 Review- en acceptatiecriteria voor frontendopbouw
Bij iedere nieuwe frontendpagina of substantiële wijziging wordt gecontroleerd:
- of bestaande OefenHub-componenten zijn hergebruikt;
- of nieuwe patronen kandidaat zijn voor de componentencatalogus;
- of MudBlazor niet buiten
OefenHub.Weblekt; - of mockup-HTML/CSS/JavaScript niet als productiecode is overgenomen;
- of styling via theme, tokens of gedeelde componentstijl loopt;
- of componenten toegankelijk, testbaar en parameteriseerbaar zijn;
- of server-side autorisatie en validatie leidend blijven.
24.6.8 Validatie-uitkomst MudBlazor- en OefenHub-componentenstrategie
De MudBlazor- en OefenHub-componentenstrategie is voldoende afgebakend voor de V1.0-baseline. De keuze is geen open ontwerpvraag meer; tijdens implementatie moet alleen nog de concrete packageversie via centraal packagebeheer worden vastgepind.
| Onderdeel | Baselineafspraak | Validatie |
|---|---|---|
| Componentbibliotheek | MudBlazor is de primaire componentenbibliotheek voor interactieve Blazor UI. | MudBlazor wordt alleen vanuit OefenHub.Web en webspecifieke tests gebruikt. |
| Packagebeheer | De MudBlazor-packageversie wordt centraal beheerd en niet per project los vastgelegd. | Package-updates lopen via review van componentcatalogus, theming, CSP en regressietests. |
| Services en providers | MudBlazor-services, providers en globale configuratie worden éénmalig in de Web-startup/layoutlaag geregistreerd. | Routeerbare pagina's registreren geen eigen globale MudBlazor-configuratie. |
| Theme | Er komt één centrale OefenHub-themeconfiguratie bovenop MudBlazor. | Kleuren, typografie, spacing, radius, elevation, states en toegankelijkheidsvarianten lopen via theme/tokens/gedeelde CSS. |
| Componentencatalogus | Terugkerende UI-patronen worden als OefenHub-componenten of wrappercomponenten vastgelegd. | Nieuwe pagina's hergebruiken bestaande componenten voordat pagina-eigen opbouw wordt toegevoegd. |
| Indeling | Herbruikbare componenten worden logisch gegroepeerd, bijvoorbeeld onder Components/Shared, Components/Layout, Components/Forms, Components/DataDisplay, Components/Feedback en domeinspecifieke submappen. | Routeerbare pagina's blijven compositielaag en bevatten zo min mogelijk visuele detailopbouw. |
| Mockupconversie | HTML-mockups zijn ontwerpinput en geen productiecode. | Review controleert dat mockup-HTML, mockup-CSS en mockup-JavaScript niet naar .razor-pagina's zijn gekopieerd. |
| Domeingrens | MudBlazor- en componenttypes lekken niet naar modulecontracten, readmodels, commands, queries of domeinmodellen. | Architecture tests en review bewaken dat UI-techniek binnen de Web-grens blijft. |
| Testaanpak | Componentgedrag, parameters, events, empty/loading/error states en wrappercomponenten krijgen bUnit-componenttests. | End-to-end smoke- en regressietests gebruiken Playwright .NET. |
Daarmee is het open punt over de MudBlazor- en OefenHub-componentenstrategie opgelost. Een latere overstap naar een andere componentbibliotheek, een tweede componentbibliotheek als structurele basis of het loslaten van de OefenHub-componentenlaag vereist een nieuw Technisch Ontwerp-besluit.
24.7 Pages
Pages bevat routeerbare Blazor-componenten. Pagina's zijn verantwoordelijk voor:
- ophalen van schermdata via page composition of query-services;
- tonen van de juiste componenten;
- starten van gebruikersacties via command-services;
- verwerken van loading-, empty-, validation- en error states;
- doorgeven van routeparameters aan server-side contracten zonder die zelf als autorisatiebewijs te behandelen.
Voorbeelden:
Pages/Public/LandingPage.razor
Pages/Account/LoginCallback.razor
Pages/Profile/ProfilePage.razor
Pages/Student/StudentFrontpage.razor
Pages/Practice/ExerciseStartPage.razor
Pages/Practice/ActiveExerciseRunPage.razor
Pages/Teacher/StudentsPage.razor
Pages/Guardian/ChildrenPage.razor
Pages/Messages/MessageOverviewPage.razor
Pages/Tickets/MyTicketsPage.razor
Pages/Admin/SiteSettingsPage.razor
24.7.1 Routeparameters
Routeparameters zijn alleen technische invoer voor de server-side controle. Zij zijn geen bewijs van toegang.
Voorbeeld:
/practice/history/{runId}
De pagina mag runId doorgeven aan een publieke reader, maar de reader moet opnieuw controleren:
- bestaat de run;
- is de run afgerond wanneer dat voor de pagina vereist is;
- hoort de run bij de actuele gebruiker of toegestane rolcontext;
- is de gevraagde actie toegestaan;
- mag de response detaildata bevatten.
24.8 Layouts
Layouts bepalen de globale schermstructuur. De exacte styling kan binnen de schermdocumentatie en implementatie worden verfijnd; de technische verantwoordelijkheden liggen vast.
| Layout | Gebruik |
|---|---|
PublicLayout | Niet-ingelogde pagina's zoals landingspagina en vaste publieke pagina's. |
AppLayout | Ingelogde hoofdapplicatie met header, footer, navigatie en profielmenu. |
ExerciseLayout | Afleidingsvrije leerling-oefencontext. |
AdminLayout | Beheerpagina's met beheercontext en beheerstructuur, indien de hoofdapplicatielayout onvoldoende is. |
ErrorLayout | Veilige 40x/50x-foutpagina's. |
De layout bepaalt visuele structuur, maar niet de autorisatie. Als een menu-item verborgen is, blijft dat alleen presentatie. De onderliggende route en moduleactie moeten server-side beschermd blijven.
24.9 Navigation
Navigation bevat modellen en helpers voor het opbouwen van zichtbare navigatie. Navigatie wordt samengesteld op basis van de server-side bepaalde sessie-, rol- en contextinformatie die via publieke contracten wordt opgehaald.
Navigatie mag:
- menu-items tonen of verbergen;
- items groeperen onder rolkoppen;
- responsief inklappen;
- badgewaarden tonen wanneer die zichtbaar mogen zijn;
- de actieve route markeren.
Navigatie mag niet:
- zelf rollen toekennen;
- zelf bepalen dat een route toegestaan is;
- autorisatie afleiden uit browserstate;
- datasets verruimen omdat een menu-item zichtbaar is;
- leerling-oefencontext verstoren met badges of overlays.
24.9.1 Responsieve navigatie
De header blijft in de basis één regel hoog. Wanneer items niet passen, worden zij gegroepeerd volgens de functionele regels uit het Functioneel Ontwerp en de schermdocumentatie. Deze groepering is uitsluitend presentatiegedrag.
| Situatie | UI-gedrag | Autorisatiegevolg |
|---|---|---|
| Leerlingcategorieën passen niet | Groeperen onder Categorieën. | Geen wijziging. |
| Rolmenu-items passen niet | Groeperen onder Beheer, Docent, Ouder/Voogd. | Geen wijziging. |
| Header wordt extra smal | Verdere groepering onder Menu. | Geen wijziging. |
| Actieve oefenrun | Badges en notificatie-indicaties visueel uitstellen. | Server-side status blijft behouden. |
24.10 State
State wordt in OefenHub bewust beperkt. De frontend mag geen tweede bron van waarheid worden naast de server-side modules.
24.10.1 Statecategorieën
| Statecategorie | Voorbeelden | Toegestaan persistent? | Bron van waarheid |
|---|---|---|---|
| Lokale componentstate | open/dicht, geselecteerde tab, tijdelijke invoer | Nee, tenzij expliciet | Component |
| Pagina-state | actieve filter, paginanummer, sortering | Soms via querystring of gebruikersvoorkeur | Modulequery of gebruikersinstelling |
| Rolcontextweergave | actieve frontpagecontext, menuweergave | Nee als autorisatiebron | Server-side context |
| Toegankelijkheidsstate vóór login | contrast, dyslexielettertype, lettergrootte | Ja, technisch beperkt | Cookie/browserwaarde tot login |
| Gebruikersvoorkeuren na login | naamweergave, sortering, verborgen waarschuwing | Ja, server-side | UserSettings binnen OefenHub.Identity |
| Oefenvoortgang | huidige vraag, antwoordstatus, totalen | Nee als browserbron | Practice-module |
| Live meekijkstate | browse-modus, geselecteerde vraag in viewer | Lokale UI-state | Live/progressbron blijft server-side |
| Notificatie- en badge-state | ongelezen teller, wacht-op-mij | Nee als bron | Communication/Support-query's |
24.10.2 Browser storage
Browser storage en cookies mogen alleen gegevens bevatten die geen persoonsgegevens, autorisatiedata, rolcontext, identity-providerdata, tokens of gevoelige payloads bevatten.
Toegestaan, mits veilig en beperkt:
- toegankelijkheidskeuzes vóór login;
- niet-autoriserende UI-voorkeuren;
- gesloten/uitgestelde systeemnotificatie voor
OncePerBrowser, voor zover functioneel zo bedoeld; - tijdelijke lokale UI-keuzes zonder domeinwaarde.
Niet toegestaan:
- rollen;
- rechten;
- gebruikers-ID's als autorisatiebron;
- kind-, leerling- of runcontext als toegangsbewijs;
- tokens of identity-providergegevens;
- antwoorden, vraagpayloads of resultaatdetails;
- systeemberichtinhoud of privéberichtinhoud.
24.11 PageComposition
PageComposition is de plek waar OefenHub.Web schermspecifieke viewmodels samenstelt uit publieke query-services van modules. Deze laag is bedoeld om te voorkomen dat Blazor-pagina's zelf veel coördinatielogica krijgen.
Voorbeeld:
PageComposition/StudentFrontpageComposer.cs
PageComposition/TeacherFrontpageComposer.cs
PageComposition/GuardianFrontpageComposer.cs
PageComposition/AdminFrontpageComposer.cs
Een composer mag meerdere query-services aanroepen:
StudentFrontpageComposer
→ Practice-query voor recent geoefend
→ Catalog-query voor beschikbare categorieën
→ Communication-query voor badges, tenzij oefencontext actief is
Een composer mag niet:
- rechtstreeks DbContexts gebruiken;
- entities uit
Data/Entitiesgebruiken; - mutaties uitvoeren;
- autorisatiebeslissingen nemen buiten modulecontracten;
- technische exceptions aan de UI doorgeven.
24.12 ViewModels
Viewmodels zijn UI-specifieke modellen. Zij mogen worden afgestemd op rendering, labels, lege toestanden, knopstatussen en lokale UI-samenstelling.
Voorbeeld:
ViewModels/Student/StudentFrontpageViewModel.cs
ViewModels/Practice/ExerciseStartPageViewModel.cs
ViewModels/Messages/MessageOverviewViewModel.cs
ViewModels/Tickets/TicketDetailViewModel.cs
ViewModels/Admin/SiteSettingsHubViewModel.cs
Viewmodels bevatten geen EF Core tracking, geen database-entiteiten en geen domeinmutatiemethoden.
24.12.1 Verschil tussen DTO, readmodel en viewmodel
| Type | Eigenaar | Doel |
|---|---|---|
| Contract-DTO | Moduleproject onder Contracts/Models. | Data-uitwisseling via publieke modulecontracten. |
| Readmodel | Moduleproject onder Models/ReadModels. | Module-eigen geoptimaliseerde leesvorm. |
| Viewmodel | OefenHub.Web/ViewModels. | Schermspecifieke rendering en UI-compositie. |
24.13 Forms en Validation
Formulieren mogen client-side gebruiksvriendelijk valideren, maar server-side validatie blijft leidend voor opslag en mutaties.
24.13.1 Formulieropbouw
| Onderdeel | Plaats |
|---|---|
| UI-formuliermodel | OefenHub.Web/Forms of ViewModels wanneer schermspecifiek. |
| Client-side validatiehulpen | OefenHub.Web/Validation. |
| Domeinvalidatie | Betreffende module. |
| Servervalidatie-resultaat | Publiek contractresultaat van moduleactie. |
| Foutweergave | Webcomponent of formulierpagina. |
Voorbeeldflow:
1. Gebruiker vult formulier in.
2. Web voert directe UI-validatie uit voor verplichte velden en invoerformaat.
3. Web stuurt command naar modulecontract.
4. Module voert autorisatie, domeinvalidatie en opslagcontrole uit.
5. Module retourneert success, validation errors of veilige foutstatus.
6. Web toont veldfouten, algemene fout of succesweergave.
Client-side validatie mag nooit worden gebruikt als enige bescherming tegen foutieve of ongeautoriseerde mutaties.
24.14 Routing en routebescherming
Routing wordt centraal beheerd binnen OefenHub.Web. Routebescherming bestaat uit twee lagen:
- grove toegang op basis van ingelogde/niet-ingelogde status en basiscontext;
- server-side object- en actiecontrole in de modulecontracten.
De eerste laag voorkomt onnodige schermtoegang. De tweede laag is leidend.
24.14.1 Routecategorieën
| Routecategorie | Voorbeelden | Controle |
|---|---|---|
| Publiek | landingspagina, vaste publieke pagina's | Geen gebruikerssessie vereist. |
| Account/provisioning | login-callback, accountfout, profielaanvulling | Identity-context. |
| Leerling | oefenen, geschiedenis, resultaten | Student-context + Practice/Catalog-controle. |
| Docent | oefenaanbod, leerlingen, online | Teacher-context + modulecontrole. |
| Ouder/voogd | kinderen, geschiedenis, online | GuardianStudent-relatiecontrole. |
| Beheerder | site-instellingen, content, accounts | Admin-context + beheerautorisatie. |
24.14.2 Routeparameters als onbetrouwbare invoer
Routeparameters worden altijd behandeld als onbetrouwbare invoer. Een pagina mag een routeparameter alleen doorgeven aan een modulecontract dat de context opnieuw valideert.
Voorbeeld:
/guardian/children/{childId}/history
De UI mag childId niet gebruiken om zelf resultaatdata te selecteren. De ouder-/voogdmodule of practice-reader moet server-side controleren of de actuele gebruiker op dat moment een actieve ouder-/voogdrelatie met het kind heeft.
24.15 Autorisatie en zichtbare acties
Zichtbare knoppen zijn geen autorisatiebewijs. Een knop kan verborgen of disabled zijn om de gebruiker te helpen, maar de actie erachter moet altijd opnieuw server-side worden gecontroleerd.
Voorbeeld:
| UI-element | Server-side controle |
|---|---|
Start nieuwe | Leerlingcontext, oefening actief, niveau/categorie/oefening toegankelijk. |
Verder gaan | Niet-afgeronde run binnen dezelfde oefening en actieve niveaucontext. |
Kijk live mee | Actieve relatie/context, actieve run, livebeschikbaarheid. |
Download PDF | Run bestaat, afgerond, viewer heeft toegang. |
Accepteer uitnodiging | Uitnodiging bestaat, pending, niet verlopen, rolcontext toegestaan. |
Melding heropenen | Actuele sluiting, heropentermijn, eigenaar van melding. |
24.16 Actieve rolcontext in de UI
De actieve rolcontext wordt server-side bepaald. De UI mag deze context tonen en gebruiken voor presentatie, maar mag haar niet zelfstandig wijzigen zonder server-side bevestiging.
Bij combinatierollen toont de frontend de samengestelde frontpage volgens de vastgelegde prioriteit. De onderliggende blokken blijven afkomstig uit afzonderlijke modulequeries en rolcontexten.
Voorbeelden:
Beheerder + Docent + Ouder/Voogd
→ beheerderblokken
→ docentblokken
→ ouder-/voogdblokken
Docent + Ouder/Voogd
→ docentblokken
→ ouder-/voogdblokken
Een gebruiker krijgt door zichtbaarheid van een blok geen extra toegang tot detailroutes. Detailroutes blijven opnieuw gecontroleerd.
24.17 Applicatieschil: header, footer en profielmenu
De applicatieschil bestaat uit header, hoofdcontent, footer, profielmenu en algemene notificatie-/popupvoorzieningen.
24.17.1 Header
De header bevat afhankelijk van context:
- logo en begroeting;
- hoofdnavigatie;
- categorie- of rolmenu's;
- berichtenicoon en badge;
- meldingenindicatie;
- profielmenu;
- responsieve groepering.
Tijdens actieve leerling-oefenruns worden afleidende signaleringen verborgen of uitgesteld. De onderliggende server-side status blijft intact.
24.17.2 Footer
De footer volgt de functionele responsiviteitsregels. De technische footercomponent mag beheerbare content ophalen via publieke beheer-/contentcontracten, maar de layoutstructuur blijft codegedreven.
24.17.3 Profielmenu
Het profielmenu toont alleen acties die voor de actuele sessiecontext relevant zijn. Toch blijft iedere achterliggende route server-side beschermd.
24.17.1 Categorie- en oefeningmenu-interactie
De Blazor-implementatie van categorie- en oefeningmenu's volgt de functionele interactieregels uit het Functioneel Ontwerp. De UI mag hover-, klik- en touchgedrag technisch verschillend afhandelen, maar mag geen afwijkende autorisatie- of datasemantiek introduceren.
| Context | Technische afspraak |
|---|---|
| Desktop hover | Mouse-over mag het menu openen, maar de component moet voorkomen dat dezelfde gebruikersintentie dubbel wordt verwerkt wanneer direct daarna een click-event volgt. |
| Desktop klik | Linker muisklik mag het menu openen of de actieve categorie wisselen, tenzij de openactie al door de voorafgaande hoveractie is verwerkt. |
| Tablet/touch | Touch/tap is de primaire trigger voor openen en wisselen van categorie. Hovergedrag mag niet nodig zijn om een oefening te bereiken. |
| Buitenklik | Een globale click-away/touch-away handler sluit het submenu wanneer buiten het menu of de categorieknoppen wordt geactiveerd. |
| Wisselen | De navigatiestate bevat maximaal één open categorie of oefeningmenu tegelijk. Openen van een andere categorie sluit de vorige. |
| Hover verlaten | Pointer-leave buiten het submenu sluit niet automatisch zolang geen andere categorie wordt geactiveerd; dit voorkomt onverwacht verdwijnen tijdens navigatie. |
De componentstate voor een open menu is tijdelijk UI-state. De lijst met categorieën en oefeningen wordt altijd opgebouwd uit server-side geautoriseerde data en mag niet uit browserstate of alleen uit eerder gerenderde markup worden afgeleid.
24.18 Afleidingsvrije oefencontext
De actieve leerling-oefenrun krijgt een eigen visuele context. De leerling moet niet worden afgeleid door communicatie- of systeemsignalen terwijl de oefening actief is.
Technisch betekent dit:
| Onderdeel | Gedrag tijdens actieve oefenrun |
|---|---|
| Berichtenbadge | Niet zichtbaar actualiseren. |
| Meldingenindicatie | Uitstellen of verbergen. |
| Systeemnotificatie-overlay | Niet tonen boven actieve oefening. |
| Popup voor niet-oefenkritieke feedback | Uitstellen tot na verlaten/afronden. |
| Oefenvoortgang | Direct server-side opslaan via Practice-module. |
| Live meekijkupdates | Blijven server-side en via realtime transport beschikbaar. |
De UI mag deze signalen visueel onderdrukken, maar mag de onderliggende ongelezenstatus, ticketstatus, notificatiegegevens of livevoortgang niet verliezen.
24.19 SignalR en realtime UI
SignalR wordt gebruikt als transport voor realtime UI-updates, zoals live meekijken, badges en online-status. SignalR is geen bron van waarheid.
Voor de frontend gelden de volgende regels:
- realtime updates worden alleen toegepast wanneer zij bij de actuele server-side context passen;
- reconnects mogen geen autorisatie overslaan;
- na reconnect wordt relevante status opnieuw opgehaald of gevalideerd;
- live meekijken blijft read-only;
- browse-modus in live meekijken is lokale UI-state;
- terugkeren naar live gebruikt de server-side actuele vraag/progressie.
Wanneer SignalR wegvalt, toont de UI een veilige fout- of disconnectstatus en mag de gebruiker geen onjuiste indruk krijgen dat de livegegevens nog actueel zijn.
Reconnect-UI volgt de baseline uit hoofdstuk 15: automatische pogingen na 0, 2, 10, 30 en 60 seconden, daarna een definitief verbroken status met een expliciete actie om opnieuw te verbinden of te vernieuwen. Tijdens reconnect worden livegegevens als tijdelijk niet-actueel gemarkeerd.
24.20 Popups, systeemnotificaties en foutweergave
Popups en systeemnotificaties hebben verschillende doelen en mogen technisch niet door elkaar lopen.
| Type | Doel | Bron |
|---|---|---|
| Popupregister-popup | Gerichte feedback, bevestiging of foutmelding bij een actie. | Admin/contentconfiguratie of vaste popupkey. |
| Systeemnotificatie-overlay | Sitebrede of doelgroepgerichte notificatie na frontpageload. | Admin-notificatiebeheer. |
| Mailbox-systeembericht | Bericht in berichtenoverzicht met eventuele domeinverwijzing. | Communication-module. |
| Formuliervalidatie | Veld- of formulierfouten. | Web + modulevalidatie. |
| Veilige foutpagina | 40x/50x-foutafhandeling. | Web/errorhandling. |
De UI toont geen technische stacktraces, SQL-fouten, tokens, payloads of interne identifiers aan eindgebruikers.
24.21 Loading states en lege toestanden
Loading states en lege toestanden zijn normale UI-toestanden en mogen niet automatisch als fout worden behandeld.
Voorbeelden van geldige lege toestanden:
- geen gekoppelde kinderen;
- geen afgeronde runs;
- geen online leerlingen;
- geen open meldingen;
- geen ongelezen berichten;
- geen filterresultaten;
- geen beschikbare vervolgactie.
Een lege toestand wordt pas een autorisatiefout wanneer de server-side module aangeeft dat de actuele gebruiker de gevraagde dataset of actie niet mag raadplegen.
24.22 Toegankelijkheid en voorkeuren
Toegankelijkheidskeuzes moeten technisch vroeg genoeg beschikbaar zijn om de UI vóór login bruikbaar te renderen. Tegelijk mag deze browserwaarde geen persoons- of autorisatiegegevens bevatten.
24.22.1 Vóór login
Toegestaan in browserwaarde:
- verhoogd contrast;
- dyslexielettertype;
- basis lettergrootte-instelling.
Niet toegestaan:
- gebruikers-ID;
- naam;
- rolcontext;
- niveaucontext;
- relatiecontext;
- identity-providergegevens;
- tokens.
24.22.2 Na login
Na login zijn server-side profielinstellingen en UserSettings binnen OefenHub.Identity leidend. De technische browserwaarde mag worden bijgewerkt zodat rendergedrag vóór een volgende login consistent blijft, maar de server-side instelling blijft de bron van waarheid.
24.22.3 Profielavatarselectie
De profielavatarselectie gebruikt alleen vooraf gedefinieerde avatars uit ProfileAvatars. OefenHub.Web toont de beschikbare avataropties via een publieke identity-query of viewmodel en stuurt bij opslaan alleen de gekozen avatar-id naar de identity-service.
| UI-onderdeel | Regel |
|---|---|
| Avataroverzicht | Toont alleen actieve en selecteerbare avatars. |
| Avataropslag | Web schrijft niet rechtstreeks naar Users.ProfileAvatarId, maar gebruikt een publiek identity-contract. |
| Uploadvelden | Niet tonen en server-side weigeren; vrije upload, externe URL's en vrije bestandsnamen zijn niet toegestaan. |
| Fallbackweergave | Bij ontbrekende, inactieve of niet langer selecteerbare avatar toont Web een standaardavatar zonder de opgeslagen waarde stilzwijgend te wijzigen. |
| Cache/readmodel | Header, profielmenu en profielpagina verversen na succesvolle wijziging via normale UI-state of readmodelinvalidatie. |
24.23 Frontend en modulecontracten
OefenHub.Web gebruikt publieke modulecontracten. De concrete implementaties blijven in moduleprojecten.
Voorbeeld:
OefenHub.Web
→ OefenHub.Practice.Contracts
→ OefenHub.Catalog.Contracts
→ OefenHub.Communication.Contracts
→ OefenHub.Support.Contracts
In de gekozen projectopbouw staan publieke contracts in het moduleproject zelf. Web mag dus het moduleproject referencen, maar niet de interne implementaties gebruiken.
Niet toegestaan:
OefenHub.Web
→ PracticeDbContext
→ ExerciseRun entity
→ direct SQL op practice.ExerciseRuns
Wel toegestaan:
OefenHub.Web
→ IExerciseRunResultReader
→ ExerciseRunResultViewDto
24.24 Dependency injection in Web
Het webproject registreert modules via extension methods, maar kent geen interne implementatiedetails.
Voorbeeldstructuur:
Extensions/
ServiceCollectionExtensions.cs
WebApplicationExtensions.cs
Voorbeeld van gewenste richting:
services.AddOefenHubIdentity(...)
services.AddOefenHubAuthorization(...)
services.AddOefenHubCatalog(...)
services.AddOefenHubPractice(...)
services.AddOefenHubCommunication(...)
services.AddOefenHubSupport(...)
services.AddOefenHubLiveMonitoring(...)
services.AddOefenHubAdmin(...)
services.AddOefenHubReporting(...)
services.AddOefenHubScheduling(...)
De exacte methode- en parameternaamgeving wordt tijdens implementatie bepaald, maar de richting is dat modules zichzelf registreren via publieke extensiepunten.
24.25 Webspecifieke validatie van projectgrenzen
Voor OefenHub.Web gelden aanvullende projectgrenzen.
| Verboden patroon | Reden |
|---|---|
Directe referentie naar Data/Entities van modules | Lekt database-entiteiten naar UI. |
| Direct gebruik van module-DbContexts | Doorbreekt modulegrenzen en autorisatielaag. |
Businessregels in .razor-componenten | Maakt domeinlogica ontestbaar en versnipperd. |
| Autorisatie op basis van alleen zichtbare knoppen | Clientstate is manipuleerbaar. |
| Persoons- of autorisatiedata in local storage | Privacy- en securityrisico. |
| Technische foutdetails in UI | Informatielek. |
Gekopieerde mockup-HTML als .razor-structuur | Maakt pagina's zelfstandig opgebouwd, slecht herbruikbaar en los van de OefenHub-componentenlaag. |
| Per pagina eigen CSS voor generieke patronen | Doorbreekt theming, consistentie, toegankelijkheid en onderhoudbaarheid. |
| MudBlazor-types in modulecontracten of domeinlagen | Lekt Web-/UI-techniek buiten de frontendgrens. |
24.26 Teststrategie voor frontend
De teststrategie voor de frontend sluit aan op het algemene testhoofdstuk.
| Testtype | Doel |
|---|---|
| Componenttests | Controleren van componentrendering, parameters, events, lege toestanden en OefenHub-wrappercomponenten rond MudBlazor. |
| Componentcatalogus-regressietests | Controleren dat gedeelde OefenHub-componenten visueel en functioneel stabiel blijven wanneer MudBlazor of theming wijzigt. |
| Page composition tests | Controleren dat viewmodels correct uit modulequery's worden samengesteld. |
| Routing tests | Controleren dat routes basiscontext afdwingen en veilige fallback tonen. |
| Authorization UI tests | Controleren dat verboden acties niet zichtbaar zijn, zonder server-side tests te vervangen. |
| Integration tests | Controleren dat Web via publieke modulecontracten werkt. |
| Accessibility tests | Controleren van contrast, toetsenbordgebruik, labels en voorkeurstoepassing. |
| Realtime UI tests | Controleren van SignalR-updategedrag, reconnect en disconnectweergave. |
| Error state tests | Controleren dat veilige foutmeldingen worden getoond. |
Architecture tests moeten aanvullend bewaken dat OefenHub.Web geen module-interne entities of DbContexts gebruikt.
De toolkeuze voor frontendtests is:
| Testlijn | Tooling | Gebruik |
|---|---|---|
| Componenttests | bUnit | Rendering, parameters, events, validatie, OefenHub-wrappercomponenten en page composition. |
| End-to-endtests | Playwright .NET | Beperkte browsergedreven smoke- en regressietests voor primaire flows. |
Nieuwe herbruikbare OefenHub-componenten krijgen bij voorkeur eerst bUnit-tests. Nieuwe pagina's krijgen alleen E2E-dekking wanneer zij onderdeel zijn van een primaire flow of een eerder gevonden regressierisico afdekken.
24.27 Implementatiechecklist
Bij het toevoegen of wijzigen van een frontendpagina moet minimaal worden gecontroleerd:
- Is de pagina in de juiste map onder
Pagesgeplaatst? - Zijn herbruikbare onderdelen als component geïsoleerd?
- Zijn bestaande OefenHub-componenten hergebruikt voordat nieuwe pagina-eigen opbouw is toegevoegd?
- Is voorkomen dat HTML-, CSS- of JavaScript uit mockups als productiecode is gekopieerd?
- Gebruikt de pagina uitsluitend publieke modulecontracten?
- Worden routeparameters server-side opnieuw gevalideerd?
- Bevat de pagina geen directe DbContext- of entitytoegang?
- Worden loading, empty, validation en error states expliciet afgehandeld?
- Is client-side validatie alleen ondersteunend?
- Wordt geen autorisatie-informatie in browserstorage opgeslagen?
- Werkt de pagina met toetsenbord en screenreaderlabels waar relevant?
- Zijn badges/notificaties correct onderdrukt tijdens actieve oefenruns?
- Zijn foutmeldingen veilig en niet technisch lekgevoelig?
- Blijft MudBlazor beperkt tot
OefenHub.Weben webspecifieke tests? - Loopt styling via theme, tokens of gedeelde componentstijl in plaats van losse pagina-CSS?
- Is de impact op Functioneel Ontwerp, Software Requirements Specification, schermdocumentatie, usecases, Technisch Ontwerp en database-informatie beoordeeld?
24.28 Implementatieverificaties
De volgende punten moeten tijdens implementatie of detailontwerp nog concreet worden gecontroleerd:
| Punt | Controle |
|---|---|
| Blazor hostingdetails | Definitieve keuze en configuratie van render-/interactiemodel per omgeving vastleggen. |
| Accessibility tooling | Bepalen welke automatische en handmatige toegankelijkheidschecks in CI of review worden opgenomen. |
| Browserstorage-wrapper | Bepalen of er één centrale wrapper komt voor veilige browserwaarde-toegang. |
| Popupintegratie | Vastleggen hoe popupregisterkeys technisch naar UI-componenten worden gemapt. |
| SignalR reconnect UI | Controleren dat 0/2/10/30-reconnectstatus, definitieve verbroken status en herlaadactie in componenttests zijn afgedekt. |
| CSP-impact | Controleren welke inline scripts/styles of third-party assets door de CSP worden geraakt. |
| Formuliervalidatie | Standaardiseren hoe servervalidatiefouten naar velden en algemene meldingen worden vertaald. |