# Programmeren in ASP.NET/Gegevenstoegang
## Inleiding

Dikwijls is het bij websites nodig om gegevens bij te houden voor de verschillende gebruikers. Dit kan nodig zijn om:
* gebruikers te laten kiezen welke informatie ze willen zien, bijvoorbeeld producten uit een webwinkel
* de gegevens opnieuw te gebruiken als de gebruiker opnieuw de site bezoekt, en als je geen cookies wil of kan gebruiken
* meerdere gebruikers te laten samenwerken, bijvoorbeeld op een forum, een wiki, of een intranet van een bedrijf
De meest gebruikte oplossing hiervoor is het bewaren van de gegevens in een databank. Bijna alle databankmerken kan je hiervoor gebruiken, en bij uitbreiding zelfs Excel, XML, of gewoon tekstbestanden. In de praktijk wordt veel Access en SQL Server gebruikt.
Een databank of database is een bestand (of bestanden) waarin op een speciale manier gegevens opgeslagen kunnen worden. Een database bevat tabellen met rijen die we records noemen. Ieder record bevat op zijn beurt velden. Voor ieder record in een tabel is de betekenis van ieder veld hetzelfde. Een tabel met adresgegevens bevat bijvoorbeeld allemaal records met de velden adres, postcode, plaats, enz.
Tabellen in een database kunnen aan elkaar gerelateerd zijn, bijvoorbeeld persoonsgegevens en adresgegevens. Ieder record in de tabel met adresgegevens bevat dan een verwijzing naar een bepaald record in de tabel met persoonsgegevens.
De databank staat meestal op de webserver zelf, maar dit hoeft niet zo te zijn. Soms gebruikt men hiervoor een aparte databankserver.
Het werken met een databank via ASP.NET kan schematisch voorgesteld worden op deze manier:
In dit hoofdstuk en de volgende leer je hoe je een verbinding maakt met een databank, en hoe je vervolgens gegevens kan lezen en schrijven.

## ADO.NET

ADO.NET is een gestandaardiseerde manier om toegang te krijgen tot verschillende soorten gegevens, vooral databanken.
ADO.NET is de opvolger van ADO (ActiveX Data Objects).
Het basiselement van ADO.NET is de DataSet. Dit vervangt wat vroeger in ASP een RecordSet-object heette.
Een DataSet is een kopie in het geheugen van de gegevens die uit een databank komen.
Het is een soort "afgekoppelde" weergave van de gegevens. Dit betekent dat de gegevens in het geheugen blijven ook zonder een actieve verbinding (connection) met de databank. Het DataSet-object is veel krachtiger dan een RecordSet in ASP. Een DataSet ds heeft namelijk een collectie "DataTable"-objecten (ds.Tables), die elk kunnen gevuld worden met gegevens uit een andere tabel van de databank. Verder is er een collectie "DataRelation"-objecten die relaties kunnen leggen tussen de DataTable-objecten.
In ADO.NET zijn er 6 belangrijke objecten die helpen bij het werken met databanken:
Van elk van deze laatste 6 objecten zijn er twee versies: één voor OLEDB (met prefix OleDb) en één voor SQL (met prefix Sql). Zo heb je naast een OleDbConnection-object ook een SqlConnection-object.
Opmerking Vanaf versie 1.1 van dotNET heeft Microsoft ook een OdbcConnection-object (voor ODBC-databanken) en een OracleConnection-object (voor Oracle-databanken) voorzien. Hiervoor moet je extra modules downloaden (zoek naar "ODBC .NET Data Provider" en "Oracle .NET Data Provider" op de Microsoft site).

## Een verbinding maken met een databank

Om met een database te kunnen communiceren, moet je een database-connectie hebben. Om deze te maken bevat ADO.NET twee objecten: het OleDbConnection-object en het SqlConnection-object. Om een dergelijk object aan te maken, moet je de databank die je wil openen opgeven in een zogenaamde connectiestring.
Voorlopig nemen we als voorbeeld de connectiestreng voor een OLEDB-verbinding met een Access-databank "adressen.mdb". Het databankbestand zet je best in de folder App_Data binnen je website. Deze folder is daar speciaal voor bedoeld, en is ook extra beveiligd. Je kan de gegevens bekijken en wijzigen binnen Visual Studio via de Database Explorer (blader naar de tabel, klik rechts en kies "Show Table Data".
De connectiestreng is dan:
Het Connection-object maak je aan op deze manier:
waarin strConn de connectiestreng is.
Nadat je zo'n object hebt aangemaakt, open je de databank met de Open-methode. Als je klaar bent met de gegevens gebruik je de Close-methode om de connectie weer te sluiten.
De connectie met de databank gaat dan als volgt:
Opmerkingen
* Je gebruikt hier een OleDbConnection-object. Om dit te gebruiken moet je twee naamruimtes importeren (System.Data en System.Data.OleDb).
* Bij het aanmaken van het OleDbConnection-object geef je de connectiestreng mee als parameter.
* Je maakt een Open- en Close-paar, waartussen je de databank kan manipuleren. Later zullen we zien dat dit bij sommige operaties weggelaten mag worden, omdat die operaties zelf zorgen voor het openen en sluiten van de connectie.
* Je gebruikt een exception-blok om mogelijke fouten op te vangen. De Close-methode schrijf je in het Finally-blok. Dit zorgt ervoor dat de connectie bij een fout niet per ongeluk open blijft staan. Anders kan je daarna problemen hebben om de connectie nogmaals te openen (bijvoorbeeld met Access-databanken).
* Bij de connectiestreng gebruik je een absoluut pad c:\inetpub\wwwroot\App_Data\adressen.mdb. Dit is de exacte locatie van de databank op de server.
* Het kan gebeuren dat je de exacte locatie van de databank niet weet. Dit gebeurt bijvoorbeeld als je de bestanden uploadt met FTP, en als je zelf niet verantwoordelijk bent voor de serveradministratie. Het kan ook gebeuren dat je de code uitprobeert op een testserver waar de locatie misschien verschillend is. Dan zou je bij het uploaden telkens de code moeten aanpassen. Beide problemen kunnen opgelost worden door een relatieve locatie te gebruiken, dit wil zeggen de locatie van de databank relatief ten opzichte van het aspx-bestand. Er bestaat een functie Server.MapPath waarmee je een relatieve locatie kan omzetten in een absolute:
* Je kan de locatie van de databank ook aangeven relatief ten opzichte van de root van de applicatie. Dan gebruik je de krul ~, die in ASP.NET de root aangeeft:
* Zie de Appendix voor de betekenis van allerlei foutmeldingen.

## Gegevens opvragen met een DataReader

Een DataReader geeft een zogenaamde forward-only en read-only verbinding naar de resultatenset. Je kan je een DataReader-object voorstellen als een datastructuur die maar één record (één rij gegevens) tegelijk kan bevatten.
Je kan de rijen maar één keer lezen van voor naar achter. Je kan de gegevens ook niet wijzigen. Het voordeel is de hogere snelheid, de datareader is daar speciaal voor geoptimaliseerd.
DataReaders worden daarom vooral gebruikt als de gegevens maar één keer gelezen moeten worden, bijvoorbeeld bij het simpelweg tonen van een lijst op een pagina. Als je daarentegen werkt met bijvoorbeeld pagineren of sorteren van gegevens, dan gebruik je een DataSet (zie verder).
Ook als je tijdens het doorlopen van de gegevens een nieuwe query wil doen, dan moet je voor de eerste query een DataSet gebruiken. Je kan immers maar één DataReader tegelijk uitlezen, maar wel meerdere DataSets.
Het DataReader-object bevat een methode Read, waarmee het eerstvolgende record ingeladen wordt. Als er geen records meer zijn, zal deze methode False teruggeven, zodat je weet dat je aan het einde van de gegevens gekomen bent. Als je daarna toch nog probeert te lezen krijg je een fout (Exception).
Verder heeft het DataReader-object verschillende methodes om de gegevens uit het record te halen (GetString(i), GetInt32(i), GetBoolean(i), enz...). Hierin is i de kolom waaruit je gegevens wil halen (de eerste kolom heeft index 0).
In de volgende voorbeelden wordt gebruik gemaakt van een tabel "AdresTabel" met volgende velden:
datareader.aspx
Opmerkingen
* In de plaats van dr("Naam").ToStringmag je ook schrijven:
* dr("Naam"), dr.Item("Naam"), en dr.GetString(1) werken alleen als het veld echt een string bevat. Als het veld bijvoorbeeld een Integer of NULL bevat, dan krijg je een foutmelding. De andere schrijfwijzen werken voor alle datatypes (en zetten die om naar strings).
* dr(1).ToString is sneller dan dr("Naam").ToString
* dr(1).ToString kan problemen geven als je later het ontwerp van de databank verandert (meer of minder kolommen, of een andere volgorde).
* de HasRows-methode wordt niet ondersteund in ASP.NET versie 1.0

### Oefeningen

* Maak een pagina waar je adressen uit een databank toont in de vorm van een HTML-tabel.
* Maak een pagina die de eerste 5 adressen toont. Hou er rekening mee dat de databank misschien minder dan 5 adressen bevat.
* Maak een pagina die alleen het adres toont van de eerste persoon in de lijst. Bij het klikken op een link, toon je de volgende persoon in de lijsttts

## Gegevens opvragen met een dataset

Je kan ook een DataSet gebruiken om gegevens uit een databank te halen. Een DataSet is een kopie van een tabel of query in het geheugen van de server. Om een DataSet te vullen heb je nog een ander object nodig: de DataAdapter (die bestaat in twee versies: OleDbDataAdapter of SQLDataAdapter). Een DataSet wordt in één keer gevuld (met de Fill-methode van de DataAdapter). Daarna kan je de gegevens gebruiken, en eventueel zelfs bijwerken in de DataSet.
De vorige pagina ziet er met een DataSet zo uit:
dataset.aspx
Opmerkingen
* Het is niet nodig om de connectie eerst te openen met cn.Open, de DataAdapter doet dit automatisch.
* Eenmaal de DataSet gevuld is, dan kan je elk onderdeel van de data afzonderlijk opvragen. Om de inhoud van rij r en kolom k op te vragen gebruik je bijvoorbeeld: ds.Tables(0).Rows(r).Item(k).ToString.
* In deze pagina is het resultaat hetzelfde, maar een DataReader is sneller en legt minder beslag op het geheugen van de server. Met een DataSet kan je de resultaten in een willekeurige volgorde doorlopen, en je kan twee operaties (bijvoorbeeld twee tabellen uitlezen) doen met dezelfde connectie.

## Een scalar opvragen

Soms moet je uit een databank maar één enkele waarde opvragen, bijvoorbeeld het aantal records met een bepaalde eigenschap. Men noemt dit een scalar-waarde.
Dit stuk code vindt bijvoorbeeld het aantal records in de ganse tabel:
scalar.aspx
Omdat de query maar één waarde als resultaat geeft, mag je ExecuteScalar gebruiken.
Opmerkingen
* ExecuteScalar kan ook een string, een DateTime of een boolean teruggeven
* Indien de query toch meerdere rijen, meerdere kolommen of allebei teruggeeft, dan is het resultaat van ExecuteScalar de waarde in de eerste kolom van de eerste rij.

### Oefeningen

* Maak een pagina die het ID van het laatste record opzoekt en toont.
* Maak een pagina die het telefoonnummer van het eerste adres in AdresTabel opzoekt.

## Parameters gebruiken

Soms hangt de query die je moet uitvoeren af van wat de gebruiker gekozen heeft. Je kan de bovenstaande code uitbreiden door de gebruiker een criterium te laten opgeven. Zo kan de gebruiker bijvoorbeeld een gemeente opgeven, en alleen de adressen uit die gemeente worden dan getoond.
In SQL gebruik je hiervoor de WHERE-clause.
Je zal dus verschillende SQL-strengen krijgen:
enz. waarbij je de echte gemeente niet op voorhand kent.
Een eerste manier om deze variabele gegevens in de SQL-streng te krijgen is met behulp van concatenatie: je plakt deze gegevens op de juiste plaats in de streng. "SELECT * FROM AdresTabel WHERE Gemeente='" & tbGemeenteVak & "';"
Let hierbij goed op dat je niet vergeet aanhalingstekens toe te voegen als het over strings gaat. Dit moeten enkele aanhalingstekens zijn (ze behoren immers tot de SQL-syntax).
scalar2.aspx (fragment)
Dit is een methode die in sommige gevallen echter problemen kan geven. Probeer bijvoorbeeld eens een naam in te vullen met een apostrof (bijvoorbeeld 's Gravenhage). Deze apostrof komt dan gewoon in de SQL-streng terecht, en de streng is niet meer correct. Om dit op te vangen, moet je in alle ingevoerde velden de apostroffen vervangen door dubbele apostroffen, vooraleer ze in de SQL-opdracht in te brengen.
Bovendien kunnen hackers proberen in het invoervak een systeemcommando in te geven. Het zou kunnen dat dit commando dan via de SQL-streng een gevaarlijke opdracht aan de server geeft. Bijvoorbeeld:
Stel: een hacker vult als gemeente de volgende waarde in:
Dit kan wel catastrofaal zijn voor je gegevens!
Om dit te vermijden, bevat het OleDbCommand-object (net zoals het SqlCommand-object) een eigenschap "Parameters", waaraan je variabele gegevens kan toevoegen. Het OleDbCommand-object zorgt er dan voor dat voor alle mogelijke datatypes de juiste syntax gebruikt wordt in de SQL-streng, en dat gevaarlijke commando's onderschept worden.
scalar3.aspx (fragment)
Opmerkingen
* In de SQL-streng vervang je de variabelen (de parameters) door een naam die je zelf kiest, maar je moet als eerste teken een apenstaart gebruiken om aan te geven dat het een parameter is:
* Je voegt de echte waarden van de parameters toe aan het Command-object cm met de opdracht:
* Merk op dat de aanhalingstekens niet meer nodig zijn rond de parameter.
* In Access mag je ook een vraagteken gebruiken:
* Als je een vraagteken gebruikte, moet je toch een string opgeven bij cm.Parameters.AddWithValue. De inhoud van de string heeft in feite geen belang.
* Let goed op: als er meerdere parameters zijn bij OLEDB (bijvoorbeeld Access), dan moeten ze toegevoegd worden in dezelfde volgorde waarin ze in de SQL-streng voorkomen. Men zegt dat de parameters positioneel zijn.
* Parameters maken het werken met datums ook veel gemakkelijker, omdat je je geen zorgen meer moet maken over het formaat, bv:
* In ASP.NET 1.x gebruik je de methode cm.Parameters.Add in plaats van cm.Parameters.AddWithValue. De rest van de code verandert niet.

### Oefeningen

* Maak weer een pagina waar je kan kiezen tussen 2 talen. Afhankelijk van de keuze wordt een andere pagina met informatie getoond, in de juiste taal. Dit keer wordt de inhoud van de pagina uit een databank gehaald (zet de inhoud van de pagina in een veld met data type "Memo").
* Maak een databanktabel met postcodes. Maak een pagina waar je de naam van een gemeente kan intikken. Het ASP.NET script zoekt de postcode van die gemeente en toont die op het scherm.
* Maak een databank met namen en wachtwoorden. Als de gebruiker met de juiste naam en het juiste overeenkomstige wachtwoord inlogt wordt de rest van de pagina zichtbaar.

## Lijsten vullen vanuit een databank
### Zonder databinding

lijstzonderbinding.aspx
Er zijn een aantal lijstcontrols die de lus hierboven (waarin de DataReader doorlopen wordt) zelf beter kunnen uitwerken. Dit noemt men databinding.

### Met databinding

Hier zullen we een keuzelijst (ListBox) vullen met namen uit een databank met behulp van databinding. Sleep de keuzelijst op de pagina, open het Smart Tag paneel, kies "Choose Data Source…", en vervolgens "<New data source…>". Je krijgt de Datasource Configuration Wizard. Met deze wizard kan je verbinden met verschillende soorten databronnen.
Het ASP.NET 2.0 framework bevat verschillende DataSource-controls die ontworpen zijn om te werken met verschillende databronnen. De pagina hier bevat een AccessDataSource-control. De SqlDataSource-control kan records opvragen van een SQL-databank, zoals SQL Server of Oracle. Er zijn ook ObjectDataSource-, XmlDataSource- en SiteMapDataSource-controls. Kies "Access Database", en vervolgens "OK". Selecteer de juiste databank (we nemen nogmaals Adressen.mdb in App_Data) en kies "Next".
In de volgende stap moet je het SELECT-statement samenstellen. Kies de juiste tabel en de juiste kolommen.
Merk op dat je optioneel een DISTINCT-clause (het aankruisvakje "Return only unique rows"), een WHERE-clause en een ORDER BY-clause kan instellen. We zullen die later gebruiken. Via "Advanced" kan je eventueel de overeenkomstige INSERT-, UPDATE-, en DELETE- commando's genereren. We gebruiken die later bij de GridView. Na "Next" kan je de verbinding testen en klikken op "Finish".
Je moet natuurlijk bij de ListBox instellen welk veld je wil zien. Dit gebeurt via "Data field to display", waardoor de property DataTextField ingesteld wordt. Je kan ook een DataValueField instellen via "Data field for the value", dit is het veld dat zal gebruikt worden als "value" voor elk item in de lijst. Kies respectievelijk "Naam" en "ID".
Als je klaar bent, ziet de HTML-code er ongeveer zo uit:
lijst.aspx
Let op de waarden voor DataTextField en DataValueField. Die waarden bepalen welke velden voor de ListBox gebruikt zullen worden.
Merk op dat er helemaal geen code meer nodig is! De AccessDataSource-control zorgt voor het ophalen van de gegevens en via de koppeling wordt de keuzelijst gevuld.
Bij het bekijken van de broncode in de browser kan je controleren dat voor elk item in de lijst (<option>-tags) het ID als value gebruikt wordt.

### Parameters gebruiken

Je kan de bovenstaande code uitbreiden door de gebruiker een criterium te laten opgeven. Zo kan de gebruiker bijvoorbeeld een gemeente opgeven, en alleen de adressen uit die gemeente worden dan getoond.
Het DataSource-object (net zoals het AccessDataSource-object) bevat een eigenschap "SelectParameters", waaraan je variabele gegevens kan toevoegen. Het AccessDataSource -object zorgt er dan voor dat voor alle mogelijke datatypes de juiste syntax gebruikt wordt in de SQL-streng, en dat gevaarlijke commando's onderschept worden.
Gebruik opnieuw de wizard om de datasource op te geven, maar gebruik nu een WHERE-clause.
lijstmetparameters.aspx

### Oefeningen

* Maak een databank met daarin een tabel met landnamen (België, Nederland, Frankrijk, ...). Maak dan een pagina met een keuzelijst waarin al deze landen voorkomen.
* Maak een pagina die een lijst met productnamen uit de tabel "Products" uit de Northwind-databank op het scherm zet.
* Maak een kleine databank met een agenda (gebeurtenissen en datums). Als de gebruiker een datum invoert, worden de gebeurtenissen van die datum getoond.