ASPNL logo (1 kb)
zaterdag 17 mei 2008




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

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 z’n 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 zo’n 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 API’s", 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 API’s 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 API’s 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 pagina’s 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 pagina’s. 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:     &copy 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 pagina’s 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.

<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL