ASP.NET 2.0: weer een grote stap voorwaarts
Een nieuwe revolutie in de ontwikkeling van webapplicaties
Door Michiel van Otegem
28 juni 2005
ASP.NET versie 1.0 was met recht een revolutie te noemen ten opzichte van zn voorganger
ASP. Versie 2.0 lijkt hetzelfde te gaan doen ten opzichte van versie 1.x. Met de komst
van versie 2.0 is ASP.NET is veel meer geworden dan een gereedschapskist met onderdelen.
Het biedt hele functieblokken met kant en klare functionaliteit die het mogelijk maken
om een applicatie te maken met een minimum aan programmeerwerk, zonder aan flexibiliteit
te verliezen.
ASP.NET 2.0 is vooral toegespitst op het verminderen van de hoeveelheid werk dat nodig
is om een applicatie te maken. Het doel van Microsoft is dat de hoeveelheid code die
geschreven moet worden verminderd wordt met zon 70%. Die doelstelling lijkt ruimschoots
gehaald te worden doordat de basis van elke applicatie vrijwel geen code meer vergt.
Overigens gaat de doelstelling absoluut niet ten koste van de snelheid, schaalbaarheid,
en beheersbaarheid van applicaties, in tegendeel. Er zijn bijvoorbeeld een aantal
wijzigingen aan het pagina/control model, waardoor het mogelijk is de ViewState te
beperken zonder verlies aan functionaliteit (wel in dynamische layout mogelijkheden).
Aangezien dit een notoir probleem is van versie 1.x, zal dit als muziek in de oren
klinken. Beheerders zullen verder blij zijn met het nieuws dat je web.config niet meer
handmatig hoeft aan te passen, maar dat hiervoor zowel een web-interface als een
snap-in voor Microsoft Management Console zal komen. De laatste van de twee is
momenteel nog niet beschikbaar, maar met de ASP.NET Web Site Administration Tool die
te zien is in Afbeelding 1 kunnen nu al zaken als beveiliging, te gebruiken mail
server, de standaard foutafhandelingspagina, en waardes in de <appsettings> sectie van
web.config veranderd worden.

Afbeelding 1, Met de Web Administration tool kan de configuratie aangepast worden
Functieblokken
De dramatische vermindering in code is met name toe te schrijven aan zogenaamde
"Building Block APIs", zeg maar functieblokken met veelgebruikte functionaliteit
voor de gemiddelde webapplicatie. Er is bijvoorbeeld een Membership API om de toegang
tot een applicatie te regelen, en een Personalization API voor het verzorgen van
personalisatie. Nu hoor ik je denken "dan ben ik zeker wel gebonden aan één gegevensbron,
want anders is die API niet te gebruiken". Aangezien dat erg inflexibel zou zijn, is
dat dus juist niet zo. De meeste functieblokken zijn gemaakt op basis van het zogenaamde
"Provider Design Pattern". Dit betekent dat je als ontwikkelaar altijd te maken hebt
met dezelfde API, ongeacht de onderliggende gegevensbron. De gegevensbron wordt aan de
API gekoppeld middels een Provider. De Provider verzorgt opslag van de gegevens, en is
er in verschillende smaken. Voor de meeste functieblokken is er een AccessProvider voor
Microsoft Access databases en een SqlProvider voor SQL Server databases. De Membership
API heeft bovendien een Provider voor Active Directory. Omdat de APIs open zijn, kun
je ook zelf een Provider implementeren, bijvoorbeeld met opslag in XML. Het Provider
model zorgt daardoor voor optimale flexibiliteit, zonder dat je veel extra
programmeerwerk hebt als je een ander opslag mechanisme wilt gebruiken. Welke Provider
gebruikt wordt, is een kwestie van configuratie, en kan dus veranderd worden zonder de
code aan te passen. Dit betekent dat je met de bestaande APIs kunt ontwikkelen in
Microsoft Access, en vervolgens in productie kunt gaan met SQL Server. Welke Provider
een API gebruikt kun je aangeven in web.config. Codevoorbeeld 1 laat zien hoe voor het
Navigation functieblok de XmlSiteMap Provider ingesteld wordt.
1: <configuration>
2: <system.web>
3: <siteMap defaultProvider="XmlSiteMapProvider"
4: enabled="true">
5: <providers>
6: <add name="XmlSiteMapProvider"
7: description="Provider voor .sitemap XML bestand."
8: type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
9: siteMapFile="web.sitemap"
10: securityTrimmingEnabled="true"/>
11: </providers>
12: </siteMap>
13: </system.web>
14: </configuration>
Codevoorbeeld 1: Provider instelling in web.config
Als je wilt, kun je de functieblokken direct gebruiken vanuit je code. Alleen dat al
scheelt behoorlijk wat programmeerwerk, omdat je de meest triviale code niet meer hoeft
te schrijven, daarvoor gebruik je de functies die de functieblokken bieden. Omdat een
functieblok een eenduidige manier biedt om bepaalde logica te verzorgen, is het ook
mogelijk om generieke controls te maken die gebruik maken van een functieblok. Je hoeft
dan niet bevreesd te zijn dat je control niet meer werkt als je besluit een andere
Provider te gebruiken. Microsoft heeft een hele verzameling controls aan ASP.NET
toegevoegd die precies dat doen. Daardoor kun je veel gebruikte functionaliteit verzorgen
op basis van een functieblok en de bijbehorende controls, zonder dat daar ook maar één
regel code aan te pas komt!
Membership
In ASP.NET 1.x vergt Forms beveiliging redelijk wat werk. Je dient een gegevensbron
met gebruikers op te zetten, een loginprocedure schrijven, en een loginformulier maken.
Verder kun je ook nog denken aan functionaliteit zodat gebruikers zich kunnen registeren,
en een mogelijkheid om een vergeten wachtwoord op te vragen. Wil je dan ook nog dat
paginas er verschillend uitzien voor aangemelde gebruikers en niet aangemelde gebruikers,
en voor verschillende rollen, en je bent echt wel een paar dagen zoet om het allemaal
voor elkaar te krijgen. In ASP.NET 2.0 is dit alles letterlijk zo gepiept met behulp
van de Membership API en de daaraan verwante Role Manager API. De Membership API bevat
functies zoals CreateUser (gebruiker aanmaken), ValidateUser (gebruiker aanmelden),
en FindUsersByEmail (gebruikers opzoeken aan de hand van het email adres). De meest
gebruikte functionaliteit is daardoor terug gebracht tot een enkele methode aanroep,
in plaats van een hele lap code om de database te openen, gegevens op te vragen, de
gebruiker te valideren, enzovoorts. ASP.NET 2.0 bevat verder een verzameling
beveiligingscontrols (zie Tabel 1), die het niet meer nodig maken om überhaupt code
te schrijven voor dit soort functies. De hele loginfunctionaliteit kun je verzorgen
door de Login-control op een pagina te plaatsen (zie Codevoorbeeld 2), en je kunt
gebruikers zich in laten schrijven met de CreateUserWizard control. Dit is uitermate
eenvoudig met slechts een tekst-editor, en slechts een kwestie van een control op een
pagina slepen in Visual Studio. Eenvoudiger kan haast niet. De controls zijn zo
gemaakt dat ze je niet in een keurslijf dwingen. De controls kunnen vrijwel op alle
manieren aangepast worden als het om de opmaak en teksten gaat, en de meeste controls
bieden bovendien de mogelijkheid om de opmaak te verzorgen via een verzameling
Templates, zodat je de opmaak helemaal zelf kunt verzorgen. Is dat nog niet genoeg
voor je, dan kun je altijd de Membership API vanuit je code aanroepen en zelf.
| Control |
Omschrijving |
| Login |
Laat een formulier zien waarin gebruikers hun gebruikersnaam en wachtwoord invoeren, en valideert de gebruiker. |
| LoginName |
Laat de naam van de aangemelde gebruiker zien. |
| Login |
Laat een formulier zien waarin gebruikers hun gebruikersnaam en wachtwoord invoeren, en valideert de gebruiker. |
| LoginStatus |
Laat zien of de huidige gebruiker aangemeld is, en geeft een link weer die de aangemelde gebruiker afmeldt. |
| LoginView |
Laat een weergave zien die afhankelijk is van of een gebruiker aangemeld is of niet, en eventueel op basis van de rol van de gebruiker. |
| ChangePassword |
Laat een formulier zien waarmee gebruikers hun wachtwoord kunnen aanpassen. |
| CreateUserWizard |
Laat een serie formulieren zien waarmee een gebruiker aangemaakt wordt. |
| PasswordRecovery |
Laat een formulier zien waarmee gebruikers hun wachtwoord (of een nieuw wachtwoord) naar hun email-adres kunnen laten sturen. |
Tabel 1, Beveiligingscontrols
1: <%@ Page Language="C#" %>
2: <html>
3: <head runat="server">
4: <title>Login formulier</title>
5: </head>
6: <body>
7: <form id="form1" runat="server">
8: <div>
9: <asp:Login ID="Login1" Runat="server">
10: </asp:Login>
11: </div>
12: </form>
13: </body>
14: </html>
Codevoorbeeld 2, Een pagina met een loginformulier
Personalisatie
Veel applicaties vandaag de dag passen zich in meer of mindere mate aan aan de gebruiker.
Dit kan expliciet zijn, door de gebruiker bijvoorbeeld de opmaak of indeling van pagina's
te laten bepalen, maar ook impliciet, door bijvoorbeeld de gebruiker keuzes te bieden
waarvan te verwachten is dat die voor de gebruiker relevant zijn. Aan dat laatste hangt
meestal een hele berg logica die bepaalt waneer iets relevant is, en dat is dan ook zeer
gebonden aan het doelgebied van de applicatie. Expliciete personalisatie daarentegen
is vrij eenvoudig, en daarom af te handelen met een standaardmechanisme. ASP.NET 2.0
biedt hiervoor een personalisatie functieblok dat even ingenieus als doeltreffend is.
Om gegevens van een gebruiker op te slaan, moet je eerst bepalen welke gegevens je wilt
opslaan. Dit kunnen eenvoudige gegevens zijn, zoals adresgegevens, maar mag ook een
complex object zijn, zoals bijvoorbeeld een winkelwagentje. Welke gegevens je wilt
opslaan leg je vast onder de <profile> sectie in web.config, zoals je kunt zien in
Codevoorbeeld 3.
1: <configuration>
2: <system.web>
3: <profile>
4: <properties>
5: <add name="Winkelmandje" type="Shop.Basket, Shop"
6: allowAnonymous="true" />
7: <group name="Adres">
8: <add name="Straat" type="System.String" />
9: <add name="Huisnummer" type="System.Int32" />
10: <add name="HuisnummerToevoeging" type="System.String" />
11: <add name="Postcode" type="System.String" />
12: <add name="Plaats" type="System.String" />
13: </group>
14: </properties>
15: </profile>
16: <anonymousIdentification enabled="true" />
17: <system.web>
18: </configuration>
Codevoorbeeld 3: Personalisatie instellingen in web.config
In Codevoorbeeld 3 zie je een aantal eigenschappen die te maken hebben met adresgegevens.
Van de eigenschappen is de naam en het type vastgelegd. Bovendien zijn de betreffende
eigenschappen gegroepeerd onder de groep "Adres". Verder is er buiten deze groep nog
een eigenschap "Winkelmandje", waarvan het volledige type, inclusief de naam van de
Assembly is aangegeven. Dat is nodig, omdat de Shop.Basket class een custom type is.
Wat verder nog van belang is, is dat deze eigenschap gemarkeerd is met allowAnonymous="true".
Hiermee wordt aangegeven dat deze eigenschap te gebruiken is voor gebruikers die nog
niet aangemeld zijn, zodat een gebruiker al zaken in het winkelmandje kan doen voordat
hij/zij aangemeld is. Wanneer de gebruiker zich aanmeldt, kan het object gekopieerd
worden naar het profiel van de aangemelde gebruiker, en zijn de andere eigenschappen
ook beschikbaar. Merk op dat de mogelijkheid tot anonieme identificatie ingeschakeld
is om anoniem gebruik van het winkelmandje mogelijk te maken.
De eigenschappen die gedefinieerd zijn in web.config zijn in de code expliciet te
benaderen met behoud van type. Dit in tegenstelling tot bijvoorbeeld waardes die
opgelsagen worden in het Session object of de ViewState. Zoals je kunt zien in
Afbeelding 2 werkt in Visual Studio zelfs de Intellisense helemaal mee, en geeft deze
het type van de eigenschap weer. Dit betekent dat je gevrijwaard bent van typefouten
die met bijvoorbeeld het Session object heel gewoon zijn. Een ander voordeel ten
opzichte van het Session object is dat de benodigde gegevens pas ingelezen worden als
ze nodig zijn, terwijl alle Session variabelen automatisch geladen worden op het
moment dat een pagina uitgevoerd wordt. Doordat je eigenschappen kunt groeperen, en
zelfgemaakte types kunt gebruiken, is het profiel mechanisme uitermate flexibel, en
voor vrijwel iedere vorm van personalisatie te gebruiken.

Afbeelding 2, Intellisense op profiel met type aanduiding
WebParts
Een onderdeel dat gebruik maakt van de personalisatiemogelijkheden van ASP.NET 2.0
zijn WebParts. WebParts zijn ook al bekend uit Sharepoint, waar ze gebruikers de
mogelijkheid geven om een pagina naar smaak in te delen. Een WebPart is een soort
control met bepaalde inhoud. Denk bijvoorbeeld aan een verzameling links, een agenda,
het laatste nieuws, enzovoorts. De gebruiker kan één of meer WebParts in daarvoor
gedefinieerde WebPartZones plaatsen. Als er meerdere zones op een pagina staan, kan
een WebPart in elk van die zones geplaatst worden. Je kunt dit zien in Afbeelding 3,
waarin vier zones gedefinieerd zijn: Top, Bottom, Left en Right. In edit-modus zijn
de zones zichtbaar zoals in Afbeelding 3. De People Finder WebPart staat nu in de
Right zone, maar zou evengoed verplaatst kunnen worden naar een van de andere zones.
Wanneer de gebruiker klaar is met het indelen van de pagina, worden de instellingen
opgeslagen in het profiel van de gebruiker, en zal de indeling de volgende keer dat
de gebruiker op de site komt weer zo zijn.

Afbeelding 3, Een pagina indelen met WebParts
ASP.NET 2.0 biedt alle controls die nodig zijn voor deze functionaliteit. Hoewel je
dit dus volledig op basis van controls opbouwt, is het nog best een klusje om door te
krijgen hoe het allemaal werkt, en valt daarom buiten het bestek van dit artikel.
Wanneer je het echter onder de knie hebt, is het heel krachtig gereedschap om een
mooie portal te maken. Zelf WebParts aanmaken is overigens wel weer heel eenvoudig
gewoon een User Control maken. Binnen de User Control kun je eigenschappen markeren
met het PersonalizableAttribute zodat ook die eigenschappen in het profiel opgeslagen
kunnen worden. Zo heb je bovenop de standaard functionaliteit van een User Control
dus wat extra mogelijkheden binnen het WebPart Framework.
Eenduidige opmaak
Een van de zaken die heel erg mist in ASP.NET 1.x is de mogelijkheid om een sjabloon
te definiëren voor meerdere paginas. De meeste ontwikkelaars lossen dit op door aan
het begin en eind van iedere pagina een (user) control te plaatsen die de algemene
opmaak van de pagina verzorgt. ASP.NET 2.0 biedt een veel elegantere oplossing in de
vorm van Master Pages. Een Master Page is een paginasjabloon. Iedere pagina kan
definiëren welk sjabloon het gebruikt. Tot zover niets bijzonders. Het aardige is
echter dat je ook sjablonen kunt maken op basis van een sjabloon. Zo kun je dus voor
delen van een applicatie een enigszins afwijkende opmaak gebruiken, maar toch
eventuele wijzigingen aan de algehele opmaak meenemen. Je kunt als het ware een hele
hiërarchie van sjablonen maken. Ook kun je tijdens het uitvoeren bepalen welke Master
Page een pagina gebruikt, zodat je bijvoorbeeld gebruikers hun eigen opmaak kunt
laten kiezen, en kun je vanuit een pagina het sjabloon programmatisch benaderen en
wijzigen.
Een Master Page werkt met zogenaamde ContentPlaceHolder controls waarmee één of
meerdere regionen in het sjabloon kunnen worden aangegeven. In de pagina plaats je
vervolgens Content control die aan de hand van de ContentPlaceHolderID aangeven voor
welke regio ze content bevatten. Codevoorbeeld 4 bevat een simpel voorbeeld van een
Master Page, en Codevoorbeeld 5 is een voorbeeld van een pagina die daar gebruik van
maakt.
1: <%@ Master %>
2: <html >
3: <head runat="server">
4: <title>.NET Magazine Master Page</title>
5: </head>
6: <body>
7: <form runat="server">
8: <h1>.NET Magazine Master Page</h1>
9: <hr />
10: <asp:contentplaceholder id="Body" runat="server">
11: </asp:contentplaceholder>
12: <hr />
13: <asp:contentplaceholder id="Footer" runat="server">
14: </asp:contentplaceholder>
15: </form>
16: </body>
17: </html>
Codevoorbeeld 4, Master Page definitie
1: <%@ Page Language="C#" MasterPageFile="~/NetMagazine.master"%>
2: <asp:Content ContentPlaceHolderID="Body" Runat="server">
3: <asp:Label ID="lbl1" Runat="server" BackColor="Green">
4: Een label in de Body ContentPlaceHolder
5: </asp:Label>
6: </asp:Content>
7:
8: <asp:Content ContentPlaceHolderID="Footer" Runat="Server">
9: © 2004 .NET Magazine
10: </asp:Content>
Codevoorbeeld 5, Pagina op basis van Master Page in Codevoorbeeld 4
Naast Master Pages zijn er nog meer mogelijkheden om een eenduidige opmaak te
verzorgen voor een applicatie: Themes en Skins. Een Theme is vergelijkbaar met een
Theme in Windows. Een Theme is een totale look & feel voor je applicatie, dus niet
alleen kleuren en lettertypes, maar ook afbeeldingen en dergelijke. Standaard zal
ASP.NET 2,0 enkele kant en klare Themes bevatten, maar helaas zijn deze in de huidige
Beta niet beschiknaat. Je kunt echter ook zelf Themes definieren aan de hand van een
.skin-bestand in een Themes\[Theme naam] map. Als je een .skin-bestand definieert,
zoals in Codevoorbeeld 6, dan kun je deze aangeven in de Page-directive van een
pagina (of Master Page), als volgt:
<%@ Page Theme="NetMagazine" %>
Je kunt een Theme ook tijdens het uitvoeren van de pagina instellen door de
Page.Theme eigenschap in te stellen, zodat deze bijvoorbeeld afhankelijk van de
gebruiker kan zijn.
In een .skin-bestand zoals in Codevoorbeeld 6 definieer je voor iedere control een opmaak, net zoals je dat in een pagina zou doen. Het enige dat je weg laat is de ID-eigenschap. Je kunt hierbij gebruik maken van de standaardopmaak mogelijkheden, CSS, en referenties aan afbeeldingen die je binnen een Theme kunt definieren.
Binnen een Theme kun je ook Skins definiëren. Een Skin is een aparte weergave voor
een control, zodat je bijvoorbeeld meerdere weergaves kunt maken van een knop, elk
met een eigen SkinID (zoals gedaan in Codevoorbeeld 6). In de paginas bepaal je
vervolgens welke knop welke Skin gebruikt, zodat bijvoorbeeld de "terug" knop er
anders uit ziet dan een "selecteer". Het codefragment in Codevoorbeeld 7 doet dit met
twee knoppen die een SkinID uit Codevoorbeeld 6 gebruiken. Het resultaat is te zien
in Afbeelding 4.
1: <asp:Button Runat="server" SkinID="Annuleer" BackColor="Red"
2: Font-Italic="True" Font-Size="Large"
3: BorderWidth="0px" ForeColor="White" />
4:
5: <asp:Button Runat="server" SkinID="OK" BackColor="Green"
6: Font-Italic="True" Font-Size="Large" Font-Bold="True"
7: BorderWidth="0px" ForeColor="White" />
Codevoorbeeld 6, Een Theme definitie met Skins
1: <asp:Button ID="Button1" Runat="server" SkinID="Annuleer"
2: Text="Annuleer" />
3: <asp:Button ID="Button2" Runat="server" SkinID="OK"
4: Text="Bestel" />
Codevoorbeeld 7, Fragment dat gebruik maakt van de Theme in Codevoorbeeld 6

Afbeelding 4, Resultaat van Codevoorbeeld 7
Nog veel meer
In dit artikel is slechts een fractie van de nieuwe functionaliteit besproken. Een
greep uit de onderwerpen die niet aan de orde zijn gekomen:
- Navigatie controls
- Databinding zonder code
- Groeperen van validatie controls
- Een postback naar een andere pagina
- Een nieuwe manier om opmaak en code te scheiden
- Een nieuw (dynamisch) compilatiemodel
- Een nieuwe aanpak bij de ondersteuning van browsers
- Cache validatie gekoppeld aan database tabellen
Je ziet dat het onmogelijk is om alles te behandelen, zeker als je bedenkt dat de
mogelijkheden van Visual Studio voor ASP.NET 2.0 vrijwel helemaal buiten beschouwing
gelaten zijn. Het is dan ook niet onverstandig om al eens met de Beta te experimenteren.
Daarmee is de leercurve straks minder, en bovendien kun je ontwikkelingen die je doet
voor de definitieve versie al enigszins op de leest schoeien van ASP.NET 2.0.
Dit artikel is eerder verschenen in .NET Magazine #6.
|