4
ASP.NET MVC
ASP.NET is het .NET raamwerk voor het bouwen van webapplicaties. De MVC variant hiervan is speciaal ontworpen voor het bouwen van dergelijke applicaties volgens het Model-View-Controller paradigma. Door het gebruik van dit paradigma in al het raamwerk in te bakken, kunnen bepaalde dingen eenvoudiger gehouden worden. E´en van deze vereenvoudigingen is dat ASP.NET MVC een aantal naamgevingsconventies gebruikt om de nood aan configuratiebestanden te omzeilen. Elke MVC applicatie is onderverdeeld in drie directories: • Controllers • Views • Models
4.1
Controllers
Zoals het MVC-paradigma zegt, zal elk binnenkomend request afgehandeld worden door een controller, dwz. een C# programma dat in de directory Controllers zit en dat een klasse definieert die overerft van Controller. De naam van de controller klasse wordt afgeleid uit de URL die wordt opgevraagd. Als de gebruiker bijvoorbeeld vraagt naar volgende URL: http://localhost/Foo/ waarbij localhost de root URL is van de ASP.NET server, dan zal er gezocht worden naar een klasse met als naar FooController in de directory Controllers. Indien deze klasse bestaat, wordt hiervan en object aangemaakt en zal dit gebruikt worden om het request af te handelen; als deze klasse niet bestaat, treedt er, zoals gebruikelijk in het HTTP protocol, een 404 error op. Als het juiste contoller object eenmaal is aangemaakt, moet er natuurlijk ook een geschikte methode van dit object worden opgeroepen. Per default zal dit de methode Index() zijn. Om een andere methode op te roepen, kan de gebruiker de naam van deze methode aangeven op het einde van de URL. De volgende URL, bijvoorbeeld: http://localhost/Foo/Bar zal ervoor zorgen dat de methode Bar() van het FooController object wordt opgeroepen. Methodes van het controller object die op deze manier opgeroepen kunnen worden, worden actie methodes (action methods) genoemd. Zij moeten een resultaat teruggeven, dat aan de gebruiker getoond kan worden. In principe is dit een object van de klasse ActionResult, maar het is ook mogelijk om bijvoorbeeld een string terug te geven, die dan automatisch in een ActionResult objecte verpakt zal worden.
1
p u b l i c c l a s s Foo : C o n t r o l l e r { p u b l i c s t r i n g Bar ( ) { r e t u r n ”
Goeiedag !
”; } } Per default worden alle methodes van een controller beschouwd als actie methodes; zij kunnen, met andere woorden, allemaal op deze manier worden opgeroepen. Indien dit niet de bedoeling is (bijvoorbeeld voor een hulpmethode die zelf niet rechtstreeks toegankelijk mag zijn voor de gebruiker), kan dit worden aangegeven met het attribuut NonAction: [ NonAction ] p u b l i c v o i d HelperMethod ( ) { ... } Als er parameters worden meegegeven met het request van de gebruiker, zijn er twee verschillende manieren om deze op te vragen. De eerste is om de Request property van de controller te inspecteren, en dan meer bepaald diens Params property. Dit is een woordenboek (dictionary) waarin de namen van de parameters gekoppeld zijn aan hun waardes. p u b l i c s t r i n g Bar ( ) { s t r i n g p = Request . Params [ ” t i j d ” ] ; i f ( p == ” morgen ” ) ; r e t u r n ”
Goeiemorgen !
”; else r e t u r n ”
Goeiedag !
”; } Indien de gebruiker nu surft naar deze URL http://localhost/Foo/Bar?tijd=morgen zal hij de begroeting “Goeiemorgen!” te zien krijgen. Er is nog tweede, handigere manier om parameters van een request te behandelen: het ASP.NET MVC raamwerk kan deze automatisch doorgeven als argument van een methode. Hiervoor is het wel nodig dat de naam van het methode-argument dezelfde is als de naam van de parameter. Dit betekent dat we voor ons voorbeeld het volgende moeten doen: p u b l i c s t r i n g Bar ( s t r i n g t i j d ) { i f ( t i j d == ” morgen ” ) ; r e t u r n ”
Goeiemorgen !
”; else r e t u r n ”
Goeiedag !
”; }
2
4.2
Models
Als de controller een binnenkomend request ontvangen heeft, is zijn eerste taak om de gevraagde operaties toe te passen op het model en/of de gevraagde gegevens op te vragen aan het model. Aangezien de model klassen normaalgezien “gewone” C# klassen zijn, is hiervoor in wezen niets speciaals nodig. Indien gewenst, kunnen deze model klassen natuurlijk communiceren met een databank, zoals uitgelegd in een voorgaand hoofdstuk.
4.3
Views
Nadat de gegevens uit het model zijn opgehaald, geeft de controller deze door aan een geschikte view klasse, die dan kan zorgen voor de visualisatie van deze gegevens voor de cli¨ent. Een view klasse is in dit raamwerk typisch een aspx bestand. Dit bevat een mengeling van HTML en C#. Hierbij kunnen C# statements worden opgenomen tussen <% ... %> en C# expressies tussen <%= ... %>. Daarnaast is er ook een hoofding nodig tussen <%@ Page ... %> waarin onder andere staat welke .NET taal (C# of VB.NET, meestal) er gebruikt wordt. Als een IDE zoals MonoDevelop gebruikt wordt, zal deze hoofding automatisch gegeneerd worden. Er bestaat twee manieren waarop een controller de gegevens die hij heeft opgevraagd aan het model kan doorgeven aan een view. In de eerste methode, gebruikt de controller zijn methode View(object Model) (dit is ´e´en van de methodes in de klasse Controller) om ´e´en object door te geven aan de view pagina. De View methode zal in de directory Views op zoek naar een pagina met dezelfde naam als de action methode waarbinnen ze wordt opgeroepen. Het resultaat van de View methode is een ActionResult object, dat de actie methode van de controller dan kan teruggeven als zijn eigen resultaat. Bijvoorbeeld, volgende code: public c l a s s FooController : Controller { p u b l i c A c t i o n R e s u l t Bar ( ) { o b j e c t o = GetModelData ( ) ; r e t u r n View ( o ) ; } } zal zoeken naar een view pagina Bar.aspx. Elke controller heeft binnen de directory Views zijn eigen subdirectory, met daarin de views van deze controller. Voor bovenstaand voorbeeld wordt de volledige locatie van de pagina dus ∼/Views/Foo/Bar.aspx (waarbij ∼ de locatie van het project voorstelt). Het object dat als argument wordt meegegeven valt in deze view pagina aan te spreken als Model, een property van het type object: <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
Test 3
Hier zijn uw gegevens: <% Model.ToString() %>
Om iets interessanters te doen met dit object Model is meestal een typecast naar het juiste soort van object nodig. Als het om ´e´en of andere reden wenselijk is om de view pagina een naam te geven die verschillend is van de naam van de actie methode, dan kan een variant van de View() methode gebruikt worden, die als eerste argument een string heeft die de naam van de view pagina voorstelt. Bijvoorbeeld: p u b l i c A c t i o n R e s u l t Bar ( ) { o b j e c t o = GetModelData ( ) ; r e t u r n View ( ” B l a b l a ” , o ) ; } Nu zal er gezocht worden naar een view pagina Views/Foo/Blabla.aspx in plaats van naar Views/Foo/Bar.aspx. De tweede manier om gegevens van de controller door te geven aan een view pagina, is door gebruik te maken van het ViewData object. Elke klasse die overerft van Controller heeft zo’n object, en bij het oproepen van een methode View(...) wordt dit object altijd integraal doorgegeven aan de view die wordt opgeroepen. Het ViewData object is een woordenboek, waarin sleutelwaarden (strings, in dit geval) kunnen gekoppeld worden aan waardes (objecten, in dit geval). Bijvoorbeeld: o b j e c t o = GetModelData ( ) ; ViewData [ ” g e g e v e n s ” ] = o ; Het oproepen van een view gebeurt nog steeds met de methode View, maar het is nu niet langer noodzakelijk om een object als argument mee te geven (hoewel dit natuurlijk nog steeds toegelaten is). p u b l i c A c t i o n R e s u l t Bar ( ) { o b j e c t o = GetModelData ( ) ; ViewData [ ” g e g e v e n s ” ] = o ; r e t u r n View ( ) ; } De pagina Views/Foo/Bar.aspx kan de gegevens dan uit ditzelfde ViewData object opvragen: <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
Test Hier zijn uw gegevens: <% ViewData["gegevens"].ToString() %> 4
Op deze manier kunnen ook meerdere objecten eenvoudig worden doorgegeven. Het blijft natuurlijk nog steeds mogelijk om in de controller een methode View(string naam) op te roepen, om views met een andere naam dan die van de actie methode te gebruiken.
4.4 4.4.1
Andere features Sessie gegevens
Indien gegevens van dezelfde gebruiker gedurende meerdere requests moeten worden bijgehouden, kan hierbij het Session object gebruikt worden. Dit is opnieuw een woordenboek, wat inhoudt dat het dus als volgt te gebruiken valt: Session [” s l e u t e l ”] = object ; Dit Session object is zowel binnen controllers als views toegankelijk. 4.4.2
Meesterpagina’s
Om de verschillende webpagina’s van een website een uniform uiterlijk te geven, is het handig om ze allemaal hetzelfde sjabloon te laten gebruiken. Hiervoor kan je een zogenaamde meesterpagina (master page) aanmaken. Dit is een HTMLbestand dat als extensie .master heeft. Dit is een voorbeeld: <%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
Mijn website Inhoud
Het speciale tag asp:ContentPlaceHolder zal dan in elke pagina van de website vervangen worden door de specifieke inhoud van die pagina. Zo zal dan bijvoorbeeld Views/Foo.aspx er nu als volgt uitzien: <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/sjabloon.master" %>
Hier zijn uw gegevens: <% ViewData["gegevens"].ToString() %> 5
De pagina hoofding (<%@ Page ...%>) bevat hier nu een referentie naar de meesterpagina. De eigenlijk inhoud van de pagina zit nu vervat in een asp:Content element, waarvan het attribuut ContentPlaceHolderID verwijst naar het ID van het asp:ContentPlaceHolder element van de meesterpagina. Het is natuurlijk ook mogelijk om in de meesterpagina meerdere plaatshouders te defini¨eren, die dan in de specifieke pagina’s worden ingevuld door meerdere asp:Content elementen; het ContentPlaceHolderID dient om de juiste inhoud te koppelen aan de juiste plaatshouder. 4.4.3
Foutenafhandeling
Er kunnen twee verschillende soorten van fouten optreden in een webapplicatie: naast de gewone HTTP foutencodes (404, enzovoort) die kunnen optreden als bijvoorbeeld een pagina niet gevonden wordt, is het ook mogelijk dat een uitzondering optreedt tijdens het uitvoeren de C# code. Als programmeur is het handig om dan een stacktrace te zien te krijgen die je vertelt waar de uitzondering juist is opgetreden, maar een eindgebruiker heeft hier vaak niet veel boodschap aan. Aan de controller klasse kan een attribuut [HandleError] worden toegevoegd om aan te geven dat er iets anders moet getoond worden dan een standaard stacktrace. Als dit attribuut geen verder informatie geef, zal er bij een uitzondering gezocht worden naar een pagina Error.aspx in de view-directory van de controller. Het is echter ook mogelijk om specifieke uitzonderingen te koppelen aan specifieke pagina’s: [ HandleError ( ExceptionType = t y p e o f ( D iv ide B yZ e r o Exc e pt io n ) , View = ” NietDelenDoorNul ” ) ] Deze [HandleError(...)] attributen kunnen zowel bij een controller klasse als geheel, als bij individuele methodes hiervan geplaatst worden. Het effect van deze attributen wordt bepaald door een instelling in het configuratiebestand Web.config, dat terug te vinden is in de root directory van de applicatie. In dit XML bestand staat een element: <customErrors mode="..." /> De waarde van het attribuut mode bepaald of je eigen foutenpagina’s effectief zullen getoond worden of niet. Indien dit attribuut de waarde mode = "On" heeft, dan zullen alle gebruikers je zelfgemaakte foutenpagina’s zijn. Indien mode = "Off", dan ziet niemand ze. De default instelling van dit attribuut is nog een derde mogelijkheid: mode = "RemoteOnly". Hierbij zien gebruikers die vanop localhost connecteren nog steeds de standaard stacktrace (dit zien immers typische de programmeurs die aan de applicatie aan het werken zijn), maar ziet de rest van de wereld je eigen foutenpagina’s. Om ook de standaard HTTP errorcodes op te vangen, kan je aan dit customErrors element een aantal error elementen toevoegen: 6
<customErrors mode="On"> <error statusCode="404" redirect="~/NietGevonden.html" />
7