Aug.29

Runtime GUI’s met Runtime databound ASP.NET Templates (ITemplate en IBindableTemplate)

Download source code

In ASP.NET v2.0 zijn heel wat verbeteringen uitgevoerd die het leven van een ontwikkelaar veraangenamen. Zo is two-way databinding mogelijk.

one- en two-way databinding
One-way databinding verzorgt het koppelen van data aan controls (ASP.NET v1 functionaliteit). Two-way databinding verzorgt eveneens het koppelen van data aan controls maar ook de terugkoppeling van de data in de controls naar de datalaag. Dus naast het tonen van data kan data na bewerking zonder tussenkomst van custom code weer worden opgeslagen.

Normaal gesproken wordt databinding in designtime geregeld. Men plaatst bijvoorbeeld een FormView op de pagina en past de templates daarvan zodanig aan dat deze overeenkomen met de op de betreffende pagina te bewerken entiteit. Bij grote websites is dit vaak veel van hetzelfde, neem bijvoorbeeld een CRM pakket. Het wordt al gauw een tijdrovende en foutgevoelige klus. Want al snel staat hier een spatie te veel en daar weer een dubbele punt te weinig, etc… In sommige gevallen kan het dus bijzonder handig zijn als pagina’s op basis van de betrokken entiteit automatisch wordt opgebouwd. Het komt de eenduidigheid en de snelheid waarmee pagina’s kunnen worden ontwikkeld ten goede.

In dit artikel wil ik aan de hand van een voorbeeld laten zien hoe onder andere runtime one- en two-way binded templates kunnen worden opgebouwd op basis van een willekeurige data klasse (entiteit). In ons geval een persoon. De belangrijkste klasse in het voorbeeld is de klasse die de koppeling van een FormView control aan een GridView control regelt. Daarnaast regelt deze klasse het koppelen van de custom FormView templates tijdens runtime. Voor het koppelen van data maken we in het voorbeeld gebruik van ObjectDataSource controls. Het is handig als je wat kennis hebt van reflection, binnenkort wil ik daar overigens ook een artikeltje aan weiden.

Omdat het hier slechts om een voorbeeld gaat zijn alle klassen opgenomen in een enkel webproject. Het project bestaat globaal uit een ListDetail klasse die de koppeling en inrichting van GridView en FormView controls verzorgt, de templates voor de FormView voor Read, Insert en Edit mode, een Persoon klasse die we gebruiken om de overige code te testen en natuurlijk een webpagina.

De sourcecode is van uitgebreidt commentaar voorzien en kan worden gedownload met behulp van de link bovenin dit artikel.

De ListDetail klasse
De ListDetail klasse heeft 3 properties. Er kan een GridView en FormView worden opgegeven. Daarnaast moet een entitytype worden opgegeven, in dit geval van het type Persoon. Verder is er een Init methode opgenomen deze dient te worden aangeroepen in de onInit methode van de webpagina.

OnInit of PageLoad
Voeren we de Init van de ListDetail uit in de pageload dan zijn we te laat om bijv. events te kunnen koppelen.

De Init methode is opgedeeld in 3 delen:

  • Initialisatie van de ObjectDataSources, 1 voor de GridView en 1 voor de FormView.
  • Initialisatie van de FormView
  • Initialisatie van de GridView
  • Voor de ObjectDataSources geldt dat zij niet op de webpagina hoeven te worden geplaatst zoals de GridView en de FormView. De ObjectDataSources worden namelijk gecreëerd in de ListDetail. Voor de IDs van de datasources wordt een vaste benaming aangehouden, mocht je echter meerdere ListDetail objecten op 1 pagina willen gebruiken dan is het beter de ObjectDataSource-ID te baseren op de gekoppelde GridView- of FormView-ID en dan met name de uniqueID van beidden. In de Init methode van de ObjectDataSources worden de ID’s van ObjectDataSources gekoppeld aan de GridView en FormView.

    ID of Control
    We koppelen niet de ObjectDataSource controls zelf. Dit kan overigens ook maar zal niet tot het gewenste resultaat leidden. Op een of andere manier komt de databinding dan niet correct tot stand. Dit heeft hoogstwaarschijnlijk te maken met state, in de state zal men geen ObjectDataSource control op willen slaan maar wel de ID daarvan.

    Vervolgens worden aan de ObjectDataSources het entitytype meegegeven (in ons geval typeof(Persoon)). Daarna moeten we opgeven met welke methoden binnen de Persoon klasse Personen kunnen worden opgehaald (Get voor de FormView, All voor de GridView), opgeslagen en verwijderd. Voor de Get methode is een parameter vereist, de ID van de op te halen persoon. Deze moet worden gekoppeld aan het geselecteerde item van de gridview. Hiervoor voegen we aan de ObjectDataSource van de FormView een ControlParameter toe de waarde van de parameter is gebaseerd op de SelectedValue property van de GridView. Daarmee is de koppeling tussen Grid- en FormView tot stand gebracht.

    In de Init methode van de GridView wordt aangegeven dat de kolommen voor de GridView en een Select button automatisch mogen worden gegenereerd. Voor een echte toepassing moet hier natuurlijk nog het een en ander gebeuren.

    In de Init methode van de FormView zetten we de defaultmode, in ons geval zie je de entity Persoon eerst ReadOnly. Het is belangrijk de propertyna(a)m(en) met de primaire sleutel van de Persoon klasse mee te geven aan zowel de FormView als de GridView. Dit doen we doormiddel van de DataKeyNames property. Voor Persoon is de property “ID” uniek.

    Als er een modificatie van de data plaats vindt binnen de FormView moeten deze ook leiden tot een tot een update van de GridView. Vandaar dat in het ItemInserted, -Deleted, -Updated event, binding van de GridView opnieuw plaatsvind. In het laatste gedeelte van de FormView Init methode worden templates gekoppeld voor ReadOnly-, Edit- en Insert-mode.

    De Templates
    De templates moeten we zelf bouwen. Normaal gesproken worden deze gedefinieerd in designtime op de webpagina. Nu we dit runtime doen zullen we in code zorg moeten dragen voor de 3 werkende templates (ReadOnly, Insert en Edit).

    De ReadOnly Template
    Voor de ReadOnly template hoeven we enkel one-way databinding te implementeren. De data hoeft namelijk enkel maar getoond te worden. Hiervoor dienen we een klasse te definieren die ITemplate implementeert, de door ons gedefinieerde FormViewReadOnlyTemplate. De ITemplate interface implementeerd 1 methode, de methode InstantiateIn(Control container). Naast deze methode definiëren we een property waarmee we op kunnen geven dat het om een Persoon klasse gaat. Deze geven we door via de ListDetail klasse.

    De InstantiateIn(Control container) methode
    In deze methode wordt de opbouw verzorgd van de template op de container. Container is de plek waar we controls in kunnen plaatsen. Doormiddel van reflection kunnen we in de InstantiateIn methode alle properties van de Persoon klasse uitvragen en op basis daarvan per property een veld tonen. De waarde wordt binnen de InstantiateIn methode nog niet gevuld er wordt echter een Label control per property opgenomen met een herkenbare ID: _bound_[PropertyName].

    Vervolgens wordt er een EventHandler aan DataBinding event van de labels gekoppeld. Op het moment dat een waarde noodzakelijk is zal de EventHandler de koppeling regelen. De EventHandler implementeren we dus ook.

    Door in de EventHandler de NamingContainer van de sender (label) op te vragen krijgen we de FormView control terug. De FormView bevat een DataItem dit is een object dat in ons geval een Persoon object is. Op basis van de opgebouwde Label ID kunnen we achterhalen welke gegevens in de betreffende label moet worden geplaatst. De ID van het Label is bijvoorbeeld “_bound_Voornaam� we weten dan dus dat de waarde van de Voornaam property van het Persoon object in de Text property van de Label moet worden geplaatst. Dit doen we doormiddel van de functie DataBinder.Eval. In designtime zouden we in de webpagina binnen het Text attribute van de label
    <label … text=’<%# Bind("Voornaam") %>’  runat=â€?serverâ€?/>
    opnemen, runtime werkt dit echter niet.

    In de InstantiateIn methode nemen we verder nog 3 LinkButtons op die het mogelijk maken een nieuwe Persoon aan te maken en de huidige persoon te bewerken of te verwijderen. We hoeven hier feitelijk niets speciaals mee te doen. Belangrijk om de linkbuttons juist te laten functioneren is de CommandName property op te geven: “New�, “Edit� en “Delete�. Om te zorgen dat bij verwijderen om een bevestiging wordt gevraagd wordt de OnClientClick property gevuld van de Delete LinkButton.

    De Edit- & Insert-Template
    Om te zorgen dat we two-way databinding kunnen toepassen hebben we niet genoeg aan de ITemplate interface maar dienen we de IBindableTemplate te implementeren. IBindableTemplate implementeert op zijn beurt ITemplate. Omdat de Insert- en Edit-template nauwelijks van elkaar verschillen qua layout overerven deze van FormViewEditableTemplate. Waarbij in de laatstgenoemde template de opbouw van het scherm wordt verzorgd binnen de InstantiateIn methode. De implementatie verschilt nauwelijks van de ReadOnlyTemplate het enige verschil is dat er geen labels maar TextBoxen op het scherm moeten worden getoond. Naast de InstantiateIn methode implementeerde IBindableTemplate ook de IOrderedDictionary ExtractValues(Control container) methode. Deze verzorgt het terugschrijven van de controlwaarden naar een object die IDictionary implementeert.

    Een Dictionary bestaat uit name-value pairs. In het “name�-deel dient de naam van de property te worden opgegeven. In het “value�-deel de waarde van het Control die de property representeerd. In de code zie je dat als eerste de gegevens uit het DataItem in de Dictionary worden geplaatst. Dit laatste is belangrijk, mochten namelijk niet alle properties van, in dit geval het Persoon object, op het scherm getoond worden dan worden de niet getoonde properties niet juist teruggeschreven naar het Persoon object. Zouden we dit dus niet doen en hebben we bijvoorbeeld een Persoon met de Voornaam “Ton� maar tonen we Voornaam niet in de template en gaan we deze Persoon bewerken en opslaan dan is de Voornaam null of “� geworden.

    Het DataItem is niet altijd beschikbaar dus dit moet ook worden gecontroleerd op null-waarde. Vervolgens worden alle properties van de betrokken entity afgelopen en wordt de Control die deze representeert erbij gezocht en de waarde in de dictionary geschreven. Nadat dit is gebeurd is de two-way databinding rond. In de 2 overerfden van de FormViewEditableTemplate verzorgen we enkel de opbouw van de footer LinkButtons die specifiek zijn voor edit en insert.

    De Persoon klasse
    In dit voorbeeld wordt een Persoon klasse gebruikt. Voor de ListDetail is het echter niet noodzakelijk dat we een Persoon klasse hebben het mag ook elke andere zelfgedefinieerde entiteit zijn. De eis is wel dat de klasse de volgende methoden (static of non-static bevat, Get(int ID), All(), Store([EntiteitType] o) en Delete([EntiteitType] o). In deze methoden kan dataaccess van welk soort dan ook worden geïmplementeerd. Voor het gemak heb ik hier een static generic List gebruikt. Leuk voor testdoeleinden maar praktisch niet handig. Hier ligt dus nog wat werk bij het ontwikkelen van “eigen� entiteiten. Verder is het van belang dat de klasse properties bevat (met getters en setters). Op zijn minst dient de klasse een ID property te bevatten van het type Int.

    De webpagina
    Op de webpagina zijn een FormView en een GridView control geplaatst. In de OnInit methode van de pagina wordt de ListDetail gecreëerd en geinitaliseerd.

    De oplossing is klaar en we kunnen nu personen zien en bewerken op 1 enkele pagina zonder voor een andere entiteiten design van een GridView of FormView designtime arbeid noodzakelijk is.

    Conclusie
    De ListDetail voorziet in een basis voor het tonen en bewerken van klassen van welk soort dan ook. Het voorziet echter nog niet in elke type property bijvoorbeeld Booleans, entiteiten die gekoppeld zijn met andere entiteiten en zo meer. Het gaat dan ook om het idee en de techniek. Voor mijn werk ben ik op dit moment bezig met een uitgebreide implementatie van deze materie. We hebben hiervoor een presentatie-framework opgezet. Op deze manier is een 2-tier of 3-tier koppeling volgens de regels der kunst snel en elegant opgelost.

    Kortom ik zou zeggen download de sourcecode en kijk er eens naar. Binnenkort een artikeltje over reflection. Op het moment dat je deze techniek zou willen adopteren is kennis van reflection een must.
    Download source code

    Aug.10

    Windows Mobile 5 Development Startup – Part IV – Remote API (ActiveSync)

    Het is alweer even geleden maar dan nu toch het laatste artikeltje over Windows Mobile 5 Development. In dit artikeltje wil ik het een en ander uit de doeken doen over de Remote API kortgezegd RAPI. De RAPI is een Windows API met behulp waarvan gecommuniceerd kan worden van een PC naar een mobile device (via ActiveSync). RAPI heb ik zelf gebruikt, zoals ik al eerder schreef, voor mijn kilometerregistratie applicatie op mijn mobile device. Ik wilde namelijk de gegevens vanaf mijn PDA kilometerregistratie applicatie naar mijn PC halen op het moment dat de PDA wordt aangesloten via ActiveSync.

    Het is niet zo heel ingewikkeld om met RAPI aan de slag te gaan dankzij de wrapper van OpenNETCF.org. Je vindt daar overigens ook veel interessante -informatie over- en -downloads voor- het Compact Framework. Om te beginnen moet de RAPI wrapper worden gedownload. Je kunt de RAPI wrapper vinden op de openNETCF site. Na het downloaden plaats je de assembly (OpenNETCF.Desktop.Communication) als reference binnen je project.

    Als eerste wil ik weten of de PDA online is via ActiveSync. De bedoeling is dat als er een PDA verbinding wordt opgebouwd via ActiveSync of verbroken, dat de applicatie dat weet. Ik heb hier een kleine simpele thread voor gebouwd die via de RAPI interface bepaald of er een verbinding is. Hieronder de code van de zogenoemde RAPIMonitor:

    In de applicatie voeg ik vervolgens de volgende code toe in de constructor van mijn Form

    De RAPIMonitor wordt gecreeerd, dan worden het OnDisconnected en OnConnected event gekoppeld. Die gaan af bij een connectie of disconnectie. Vervolgens wordt de thread gestart. De applicatie loopt dus gewoon door en op de achtergrond kijkt de monitor of er een verbinding is of niet. Tot hier even over de RAPI monitor. Meer details op aanvraag zou ik zeggen ;-).

    In het OnConnected EventHandler heb ik code geplaatst die een backup maakt van de SQL Mobile database waar de PDA applicatie op draait. Dit gaat alsvolgt:

    Het bestand kmmanager.sdf wordt nu vanaf de PDA gekopieerd naar “c:\kmmanager.sdf” op de desktop PC.

    Omdat ik een nederlandstalige Windows Mobile versie heb staat de software in “programmabestanden” in de engelstalige versie is dit uiteraard anders, “program files”.

    Er zijn uiteraard meerdere file functies in de RAPI wrapper te vinden. Zo kunnen bestanden van de desktop naar de PDA worden gekopieerd, gecontroleerd worden of een file bestaat op de PDA, het creeeren van directories en snelkoppelingen.

    Het leuke is dat er vanaf de desktop ook processen kunnen worden gestart op de PDA. Dat was in mijn geval te gebruiken voor het laten maken van een export bestand door de kmmanager PDA applicatie. Het starten van een proces gaat alsvolgt:

    Op de PDA wordt nu mijn applicatie gestart met als argument “export”. In mijn geval heb ik de applicatie zo ingericht dat er alleen een export wordt gemaakt en de applicatie daarna weer wordt afgesloten dankzij dit argument.

    Met behulp van bovengenoemde functie kunnen ook cab-files worden geinstalleerd. In de desktop applicatie kan bijvoorbeeld de PDA applicatie worden opgenomen. Staat de software nog niet op de PDA dan wordt de cab-file gekopieerd naar de PDA en moet de installatie op afstand worden gestart. Het starten van de installatie van een cab file werkt echter net iets anders, je moet het even weten.

    Tot zover. Meer over RAPI kun je uiteraard lezen op de website van OpenNETCF.

    Dit is dan ook het laatste artikeltje wat betreft Windows Mobile. Binnenkort een artikeltje over heel wat anders. Het runtime genereren van een ASP.NET 2.0 bindable template voor bijvoorbeeld een FormView. Het interessante van deze materie is dat het perspectieven biedt bij het maken van gegenereerde data-gedreven webpagina’s op een “nette” manier.