Application State Services in Blazor
- Inleiding
- Inlichtingendienst.cs
- MainLayout.razor
- Werknemer.razor – inlichtingen vergaren
- InlichtingenHQ.razor – reageer op event
- InlichtingenHQ.razor – desinformatie verspreiden
- Werknemer.razor – reageer op event
- Slot
Inleiding
In de post over Binding hebben we laten zien hoe gegevens vanuit een hoofdcomponent worden doorgegeven naar een subcomponent (one-way binding) en omgekeerd (two-way binding). We hadden het één ander geïllustreerd met een fictief bedrijf waarbij de CEO een hoofdcomponent is en de onderliggende partner en medewerkers subcomponenten.
Er is dus interactie mogelijk tussen hoofdcomponenten en subcomponenten, maar hoofdcomponenten willen ook met elkaar communiceren. Verder is er ook de behoefte dat als je weer terugkomt bij een component je wilt zien wat de vorige keer bij dat component is ingevoerd (Application State Management) en niet dat je de initiële defaultwaarden weer te zien krijgt. We zullen in deze post een techniek demonstreren dat communicatie tussen (hoofd)componenten mogelijk maakt en dat een component laat reageren op een event van een andere component. Met deze techniek wordt een (beperkte) vorm van Application State bereikt waarbij de Application State is opgetuigd als een service.
Het één en ander zullen we illustreren met het fictieve bedrijf dat we in de posting over Binding hebben gebruikt. Het bedrijf krijgt te maken met een genadeloze overzeese concurrent die niet vies is van bedrijfsspionage en de hand ook niet omdraait voor infiltratie en sabotage. De voorbeeldcode kun je hier (GitHub) vinden.
Inlichtingendienst.cs
Een overzeese nieuwkomer heeft de lokale markt betreden en één van haar belangrijkste doelen is de eliminatie van de lokale concurrentie. De overzeese nieuwkomer vindt alle methoden (ook onorthodoxe) geoorloofd voor het doen behalen van voordeel op de concurrentie.
De overzeese nieuwkomer heeft een inlichtingendienst en een afdeling wordt opgericht voor de desbetreffende lokale markt. We definiëren binnen onze Blazor applicatie klasse Inlichtingendienst. De klasse bevat drie properties (InlichtingenLand, InlichtingenProduct en Desinformatie) welke de informatie gaat bevatten die interessant is voor de Inlichtingendienst.
public class Inlichtingendienst
{
public string InlichtingenLand
{ get; private set; }
public string InlichtingenProduct
{ get; private set; }
public bool DesInformatie
{ get; private set; }
Inlichtingen vergaren
De inlichtingendienst gaat voortvarend te werk en infiltreert een concurrerend bedrijf door bij dat bedrijf een inlichtingenofficier te stationeren. De inlichtingenofficier vergaart de inlichtingen voor de Inlichtingendienst en zo staat methode VergaarInlichtingen voor het vergaren van informatie waarbij de properties van waarden worden voorzien.
// A1. Vergaren door te spelen inlichtingen
public void VergaarIichtingen(
ComponentBase Bron,
string InlichtingenLand,
string InlichtingenProduct)
{
// Verzamel de gegevens
this.InlichtingenLand = InlichtingenLand;
this.InlichtingenProduct = InlichtingenProduct;
// Aangeven dat de inlichtingen zijn
// vergaard en verstuurd kunnen worden
// naar de Inlichtingendienst
InlichtingenSamengesteld(Bron,
InlichtingenLand,
InlichtingenProduct);
}
Private method InlichtingenSamengesteld wordt aangeroepen zodra de inlichtingen zijn vergaard en de method zorgt ervoor dat event InlichtingenVerstuurd wordt opgewekt.
// A2. Signaal naar de Inlichtingendienst
// zodat die weet dat de inlichtingenofficier
// gegevens heeft gestuurd
private void
InlichtingenSamengesteld(ComponentBase Bron,
string InlichtingenLand,
string InlichtingenProduct)
=> InlichtingenVerstuurd?.Invoke(Bron,
InlichtingenLand,
InlichtingenProduct);
De opgewekte event is een signaal dat opgemerkt wordt door de Inlichtingendienst en de Inlichtingendienst zal n.a.v. dat signaal dingen ondernemen opdat de inlichtingen binnengehaald worden.
// A3. Signaal dat gedetecteerd wordt door
// de Inlichtingendienst, inlichtingen
// van de inlichtingenofficier
public event Action
<ComponentBase, string, string>
InlichtingenVerstuurd;
Desinformatie verspreiden
De Inlichtingdienst houdt zich ook bezig met het laten verspreiden van desinformatie binnen het concurrerende bedrijf zodat de activiteiten van het desbetreffende bedrijf gesaboteerd worden en zo staat methode MaakDesinformatie voor het doen opstellen van de desinformatie.
// B1. Samenstellen Desinformatie
public void MaakDesInformatie(
ComponentBase Bron,
string InlichtingenLand,
string InlichtingenProduct,
bool DesInformatie)
{
// De te versturen desinformatie
this.InlichtingenLand = InlichtingenLand;
this.InlichtingenProduct = InlichtingenProduct;
this.DesInformatie = DesInformatie;
// Aangeven dat de desinformate is
// samengesteld enverstuurd kan worden
// naar de inlichtingenofficier
DesInformatieSamengesteld(
Bron,
InlichtingenLand,
InlichtingenProduct,
DesInformatie);
}
Private method DesinformatieSamengesteld wordt aangeroepen zodra de desinformatie klaar staat voor verspreiding en de method zorgt ervoor dat event DesinformatieVerstuurd wordt opgewekt.
// B2. Signaal naar de Inlichtingenofficier
// zodat die weet dat de Inlichtingendienst
// desinformatie heeft gestuurd
private void
DesInformatieSamengesteld(
ComponentBase Bron,
string InlichtingenLand,
string InlichtingenProduct,
bool DesInformatie)
=> DesInformatieVerstuurd?.Invoke(
Bron,
InlichtingenLand,
InlichtingenProduct,
DesInformatie);
De opgewekte event is een signaal dat opgemerkt wordt door de inlichtingenofficier en de inlichtingenofficer zal n.a.v. dat signaal de desinformatie verspreiden.
// B3. Signaal dat gedetecteerd wordt door
// de Inlichtingenofficier,
// desinformatie van de Inlichtingendienst
public event Action<ComponentBase,
string,
string,
bool> DesInformatieVerstuurd;
En de Inlichtingendienst “infecteert” zichzelf bij haar host als een service zodat zij haar heimelijke overzeese invloed kan doen gelden op bijna ieder aspect van de applicatie:
namespace BlazorBlogProject.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder =
WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp
=> new HttpClient { BaseAddress =
new Uri(builder.HostEnvironment.BaseAddress) });
// Model
builder.Services.AddScoped
<IModel, MemoryModel>();
// builder.Services.AddScoped
//<IModel, DbModel>();
// ViewModel
builder.Services.AddScoped
<IVoegToeViewModel,
VoegToeViewModel>();
builder.Services.AddScoped
<ITotaalAantalViewModel,
TotaalAantalViewModel>();
// Inlichtingendienst
builder.Services.AddScoped
<Inlichtingendienst>();
await builder.Build().RunAsync();
}
}
}
MainLayout.Razor
Layoutbestand MainLayout.razor bestaat uit twee gebieden. Een gebied is gereserveerd voor een vaste menubalk zijnde de <NavMenu/>-tag en er is een gebied met een dynamische inhoud waarbij elke component zijn pagina in de @Body placeholder laadt.
Voor het in de gaten kunnen houden van de componenten is layoutbestand MainLayout.razor de perfecte plek en de Inlichtingendienst claimt dan ook een plekje in dat layoutbestand. De Inlichtingendienst zet er zijn “kantoor” neer welke is te herkennen aan de <InlichtingenHQ>-tag.
@inherits LayoutComponentBase
<div class="content px-4">
<NavMenu />
<div class="row justify-content-start">
<div class="col-8">
@Body
</div>
<div class="col-4">
<InlichtingenHQ />
</div>
</div>
</div>
<InlichtingenHQ> heeft zich succesvol genesteld in layoutbestand MainLayout.razor. De Inlichtingendienst gaat zijn voelsprieten richten op Ceo.razor zijnde het concurrerende bedrijf dat geïnfiltreerd gaat worden. De pagina’s van de componenten worden ingeladen in de @Body placeholder en de inlichtingendienst komt in actie zodra Ceo.razor ten tonele verschijnt.
Werknemer.razor – inlichtingen vergaren
CEO.razor staat voor het concurrerende bedrijf dat geïnfiltreerd gaat worden. Binnen dat bedrijf regeert de CEO met ijzeren vuist waarbij de CEO zijn werknemers als de allerlaagste levensvormen beschouwt. Werknemers hebben niks in te brengen en durven ook niks in te brengen tegen de bevelen van bovenaf.
Het schrikbewind binnen CEO.razor biedt mogelijkheden voor de Inlichtingendienst. Veel huidige en voormalige medewerkers koesteren wrok jegens de CEO en het is dan ook niet moeilijk om mensen te vinden die voor de Inlichtingendienst willen werken als agent of als inlichtingenofficier.
De Inlichtingendienst heeft een inlichtingenofficier gestationeerd binnen Werknemer.razor en voor het onderhouden van contact met de Inlichtingendienst wordt klasse Inlichtingendienst geïnjecteerd. De IDisposable interface wordt ook geïmplementeerd. Dat is nodig om het contact te verbreken met de Inlichtingendienst zodra Werknemer.razor wordt afgesloten zodat geen memory leaks achterblijven die gehackt kunnen worden.
@using BlazorBlogProject.Client.Inlichtingen
@inject Inlichtingendienst Inlichtingendienst
@implements IDisposable
De bevelen van bovenaf worden uitgevoerd en de inlichtingenofficier stuurt de inlichtingen naar de InlichtingenHQ waarvoor methode .VergaarInlichtingen() van klasse Inlichtingendienst wordt gebruikt. Het gaat in dit geval om het versturen van inlichtingen naar de Inlichtingendienst en niet om het verspreiden van desinformatie.
if (!Inlichtingendienst.DesInformatie)
{
// Inlichtingen doorspelen naar
//de Inlichtingendienst
Inlichtingendienst.VergaarInlichtingen(this,
Land,
Product);
}
We zagen dat vanuit methode .VergaarInlichtingen() private method .InlichtingenSamengesteld() wordt aangeroepen welke ervoor zorgt dat event InlichtingenVerstuurd wordt opgewekt. De opgewekte event is een signaal dat opgemerkt wordt door InlichtingenHQ.razor die n.a.v. dat signaal dingen gaat ondernemen opdat de inlichtingen binnengehaald worden.
InlichtingenHQ.razor – reageer op event
De Inlichtingendienst moet contact kunnen krijgen met de inlichtingenofficier en daarvoor wordt in InlichtingenHQ.razor klasse Inlichtingendienst geïnjecteerd. Klasse Inlichtingendienst wordt door de Inlichtingendienst (in InlichtingenHQ.razor) en de inlichtingenofficier (in Werknemer.razor) gebruikt.
De IDisposable interface wordt ook geïmplementeerd. Dat is nodig om het contact te verbreken met de inlichtingenofficier zodra InlichtingenHQ.razor wordt afgesloten zodat geen memory leaks achterblijven die gehackt kunnen worden.
@using BlazorBlogProject.Client.Inlichtingen
@inject Inlichtingendienst Inlichtingendienst
@implements IDisposable
Vanuit Werknemer.razor is event InlichtingenVerstuurd opgewekt en het is de bedoeling dat de event opgemerkt wordt door de Inlichtingendienst. De Inlichtingendienst hangt aan het signaal van de inlichtingenofficier een eigen actie en Task InlichtingenDieZijnBinnengekomen dient uitgevoerd te worden zodra het signaal van de inlichtingenofficier wordt opgemerkt:
protected override void OnInitialized()
{
// De Inlichtingendienst is geopend,
// kunnen detecteren dat er vanuit het werkterrein
// Inlichtingen zijn verstuurd
Inlichtingendienst.InlichtingenVerstuurd +=
async (Source,
InlichtingenLand,
InlichtingenProduct) =>
await InlichtingenDieZijnBinnengekomen(
Source,
InlichtingenLand,
InlichtingenProduct);
}
De connectie met de inlichtingenofficier mag niet langer openstaan dan nodig en het contact dient verbroken te worden zodra InlichtingenHQ.razor wordt afgesloten. Het verbreken van het contact en het niet meer hoeven op te merken van signaal InlichtingenVerstuurd geschiedt als volgt waarvoor de IDisposable interface nodig is:
void IDisposable.Dispose()
{
// Verbinding met werkterrein verbreken
// als de Inlichtingendienst is gesloten
Inlichtingendienst.InlichtingenVerstuurd -=
async (Source,
InlichtingenLand,
InlichtingenProduct) =>
await InlichtingenDieZijnBinnengekomen(
Source,
InlichtingenLand,
InlichtingenProduct);
}
Actie InlichtingenDieZijnBinnengekomen is de actie die de Inlichtingendienst uitvoert naar aanleiding van het signaal van de inlichtingenofficier. In dit geval gaat het om het refreshen van de UI, maar andere acties kunnen ook uitgevoerd worden zoals het wegschrijven naar een database. Voordeel is dat meerdere dingen mogelijk zijn n.a.v. het optreden van een event bij een andere component, maar je moet wel aangeven wat er moet gebeuren. Als de UI gerefreshed moet worden dan moet je dat ook aangeven want dat gebeurt niet automatisch.
private async Task InlichtingenDieZijnBinnengekomen(
ComponentBase Source,
string InlichtingenLand,
string InlichtingenProduct)
{
// Alleen iets doen als het signaal
// van een andere component komt
if (Source != this)
{
// refresh UI
await InvokeAsync(StateHasChanged);
}
}
De UI van InlichtingendienstHQ.razor bestaat uit variabelen van klasse Inlichtingendienst:
Deze inlichtingen hebben we ontvangen van
onze inlichtingenofficier,<br />
de concurrent richt zich op:<br />
<ul>
<li>Dit land:
<b>@Inlichtingendienst.InlichtingenLand</b>
</li>
<li>
Met dit product:
<b>@Inlichtingendienst.InlichtingenProduct</b>
<br />
</li>
</ul>
En we zien dat de inlichtingen vanuit Werknemer.razor bij InlichtingenHQ.razor terechtkomen en na een refresh zichtbaar worden in InlichtingenHQ.razor.
InlichtingenHQ.razor – desinformatie verspreiden
De Inlichtingendienst heeft een inlichtingenofficier gestationeerd binnen Werknemer.razor waardoor de Inlichtingendienst precies weet wat de concurrent doet. Bedrijfsspionage is niet voldoende om de lokale concurrentie geëlimineerd te krijgen en de Inlichtingendienst krijgt dan ook fiat voor sabotage activiteiten middels het verspreiden van desinformatie.
De UI van de Inlichtingendienst van waaruit opdracht wordt gegeven tot het verspreiden of tot het stopzetten van de verspreiding van desinformatie:
Verstuur desinformatie:<br />
Land
<input @bind="DesInformatieLand"
class="form-control col-sm-6" />
Product
<input @bind="DesInformatieProduct"
class="form-control col-sm-6" />
<button class="btn btn-danger"
@onclick="Verstuur">
Start infiltratie
</button><br />
<br />
<button class="btn btn-info"
@onclick="Annuleer">
Stop infiltratie
</button>
De Inlichtingendienst stuurt de te verspreiden desinformatie naar de inlichtingenofficier waarvoor methode .MaakDesInformatie() van klasse Inlichtingendienst wordt gebruikt met boolean waarde true.
void Verstuur()
{
Inlichtingendienst.MaakDesInformatie(
this,
DesInformatieLand,
DesInformatieProduct,
true);
}
Het tijdelijk of permanent stopzetten van de verspreiding van desinformatie moet ook mogelijk zijn omdat de concurrent anders een vermoeden gaat krijgen dat er een mol is binnen haar organisatie. Het stopzetten geschiedt ook met methode .MaakDesInformatie() van klasse Inlichtingendienst waarbij de boolean de waarde false heeft.
void Annuleer()
{
Inlichtingendienst.MaakDesInformatie(
this,
DesInformatieLand,
DesInformatieProduct,
false);
}
Methode MaakDesInformatie roept binnen klasse Inlichtingendienst private method DesinformatieSamengesteld aan zodra de desinformatie klaar is voor verspreiding en de method zorgt ervoor dat event DesinformatieVerstuurd wordt opgewekt. De event is een signaal voor de inlichtingenofficier in het veld om de desinformatie te verspreiden of om de verspreiding juist stop te zetten.
Werknemer.razor – reageer op event
Vanuit InlichtingendienstHQ.razor is event DesinformatieVerstuurd opgewekt en het is de bedoeling dat de event opgemerkt wordt door de inlichtingenofficier. De inlichtingenofficier hangt aan het signaal van de Inlichtingendienst een eigen actie en Task VerwerkDesInformatie dient uitgevoerd te worden zodra het signaal van de Inlichtingendienst wordt opgemerkt:
protected override void OnInitialized()
{
// Verbinding gelegd met de Inlichtingendienst,
// de inlichtingenofficier staat open
// voor signalen vanuit de Inlichtingendienst
// dat de Inlichtingendienst desinformatie
// heeft gestuurd dat verwerkt gaat worden
// om de concurrent te saboteren
Inlichtingendienst.DesInformatieVerstuurd +=
async (Source,
InlichtingenLand,
InlichtingenProduct,
DesInformatie) =>
await VerwerkDesInformatie(Source,
InlichtingenLand,
InlichtingenProduct,
DesInformatie);
}
De connectie met de Inlichtingendienst mag niet langer openstaan dan nodig en het contact dient verbroken te worden zodra Werknemer.razor wordt afgesloten. Het verbreken van het contact en het niet meer hoeven op te merken van signaal DesinformatieVerstuurd geschiedt als volgt waarvoor de IDisposable interface nodig is:
void IDisposable.Dispose()
{
// Verbinding verbreken met de
// Inlichtingendienst als de inlichtingen-
// officier niet meer ter plekke is
Inlichtingendienst.DesInformatieVerstuurd -=
async (Source,
InlichtingenLand,
InlichtingenProduct,
DesInformatie) =>
await VerwerkDesInformatie(
Source,
InlichtingenLand,
InlichtingenProduct,
DesInformatie);
}
Actie VerwerkDesInformatie is de actie die de inlichtingenofficier uitvoert naar aanleiding van het signaal van de Inlichtingendienst. In dit geval gaat het om het refreshen van de UI, maar andere acties kunnen ook uitgevoerd worden zoals het wegschrijven naar een database. Voordeel is dat meerdere dingen mogelijk zijn n.a.v. het optreden van een event bij een andere component, maar je moet wel aangeven wat er moet gebeuren. Als de UI gerefreshed moet worden dan moet je dat ook aangeven want dat gebeurt niet automatisch.
private async Task VerwerkDesInformatie(
ComponentBase Source,
string InlichtingenLand,
string InlichtingenProduct,
bool DesInformatie)
{
// Alleen iets doen als het signaal van
// een andere component komt
if (Source != this)
{
// refresh UI om de desinformatie van
// de Inlichtingendienst te activeren
// of te deactiveren
await InvokeAsync(StateHasChanged);
}
}
De UI van Werknemer.razor is “geïnfecteerd” met de variabelen van klasse Inlichtingendienst en dat heeft als gevolg dat de werknemers alleen de desinformatie zien van de Inlichtingendienst zodra de Inlichtingendienst methode MaakDesInformatie() heeft opgestart met boolean waarde true.
@if (Inlichtingendienst.DesInformatie)
{
<span>
Lanceer een verkoopcampagne voor:
<b>@Inlichtingendienst.InlichtingenLand</b>
<br />
Te verkopen product:
<b>@Inlichtingendienst.InlichtingenProduct</b>
<br />
</span>
}
else
{
<span>
Lanceer een verkoopcampagne voor:
<b>@Land</b><br />
Te verkopen product:
<b>@Product</b><br />
</span>
}
De verspreiding van de desinformatie wordt gestopt zodra methode MaakDesInformatie() met boolean waarde false wordt opgestart. De werknemers krijgen dan weer de bevelen te zien van de CEO en de Partner van de CEO.
De inlichtingenofficier laat de Inlichtingendienst als volgt weten dat de desinformatie is verspreid binnen het concurrerende bedrijf:
if (Inlichtingendienst.DesInformatie)
{
// De inlichtingenofficier heeft
//de desinformatie geactiveerd
Console.WriteLine("infiltratie succesvol -> " +
Inlichtingendienst.InlichtingenLand);
Console.WriteLine("infiltratie succesvol -> " +
Inlichtingendienst.InlichtingenProduct);
// De desinformatie weer terugsturen naar
// de Inlichtingendienst zodat die weet dat
// de desinformatie is geactiveerd
Inlichtingendienst.VergaarInlichtingen(
this,
Inlichtingendienst.InlichtingenLand,
Inlichtingendienst.InlichtingenProduct);
}
En we zien dat de desinformatie vanuit InlichtingenHQ.razor bij Werknemer.razor terechtkomt en na een refresh zichtbaar wordt in Werknemer.razor. De partner van de CEO heeft opdracht gegeven tot het versturen van gezichtsbruiners naar Svalbard, maar de informatie bereikt de werknemers niet. Ze krijgen de desinformatie te zien van de Inlichtingendienst dat Zonnestudio’s naar Somalië verscheept moeten worden.
En we zien een (beperkte) vorm van State Management wat wordt veroorzaakt doordat klasse Inlichtingendienst zichzelf heeft “geïnfecteerd” bij haar host als een AddScoped service:
builder.Services.AddScoped<Inlichtingendienst>();
De geactiveerde desinformatie bij Werknemer.razor wordt niet vergeten. We zien dat de desinformatie nog steeds van kracht is als we een uitstapje maken naar een andere component en weer terugkomen bij Werknemer.razor. Van “echte” State Management kunnen we eigenlijk niet spreken omdat alles is vergeten zodra de applicatie wordt afgesloten.
Dat alles wordt onthouden als de applicatie wordt afgesloten en een volgende keer weer wordt opgestart, daar zijn ook weer constructies voor om dat voor elkaar te krijgen. Maar dat is een ander onderwerp dat we niet in deze post gaan behandelen.
Slot
In deze post heb ik een techniek gedemonstreerd waarmee interactie mogelijk is tussen (sub) componenten. Componenten kunnen een event “de deur laten doen uitgaan” waarbij een andere component de event signaleert en eigen acties aan de event koppelt. Met deze techniek wordt een (beperkte) vorm van Application State bereikt als de Application State als een service is opgetuigd.
Het één en ander heb ik geïllustreerd met een component dat staat voor een (vreemde) Inlichtingendienst (InlichtingenHQ.razor) en een subcomponent dat staat voor een medewerker van een bedrijf (Werknemer.razor) die gerecruteerd is als inlichtingenofficier voor de Inlichtingendienst.
Vanuit Werknemer.razor worden inlichtingen doorgespeeld naar de Inlichtingendienst waarbij de Inlichtingendienst de inlichtingen verwerkt. InlichtingenHQ.razor stuurt desinformatie naar de inlichtingenofficier waarbij de inlichtingenofficier de ontvangen desinformatie verspreidt. Beide componenten laten events uitgaan en de andere component reageert daarop (InlichtenHQ die de ontvangen inlichtingen verder verwerkt en de inlichtingenofficier die de desinformatie gaat verspreiden of laat stopzetten).
Het lijkt me zeer onwaarschijnlijk dat Blazor gebruikt zal worden voor spionagescenario’s zoals beschreven in deze post. Het is maar een verhaal ter illustratie, maar het verhaal illustreert wel hoe iets opgezet kan worden in Blazor waardoor componenten in een Blazor applicatie op elkaar kunnen reageren en elkaar zelfs kunnen manipuleren.
Hopelijk ben je met deze posting weer wat wijzer geworden en ik hoop je weer terug te zien in één van mijn volgende blog posts. Wil je weten wat ik nog meer over Blazor heb geschreven? Hit the Blazor button…