- Posted by Jakob Andersen on March 23, 2008
(Once again a post in danish, this time an introduction to MonoRail building an blog based on the datamodel from the previous posts. Again if you want this content in english let me know) Introduktion
I de tre forrige indlæg har jeg beskrevet Castle ActiveRecord og hvordan vi ved at udnytte atributter i vores klasser kan undgå at skrive en masse triviel kode til databehandling of forespørgsler. Jeg har brugt en datamodel for et blogsystem og i dette indlæg vil jeg vha. Castle MonoRail påbegynde at implementere selve blogapplikationen ved at benytte os af vores ActiveRecord klasser.
Model View Controller
Castle MonoRail er et Model View Controller framework, men hvad er det? Model View Controller(MVC) er et designpattern hvis formål er at holde vores præsentationskode adskildt fra vores forretningsklasser. Ydermere adskiller det præsentationskoden fra selve præsentationen.
Model er vores klasser der indeholder data og funktionalitet til at arbejde på disse data. I vores tilfælde vil det være vores ActiveRecord klasser men det er værd at bemærke at ActiveRecord ikke er en nødvendighed for at bruge MVC.
View er vores brugergrænseflades udseende. I vores tilfælde vil det være filer med HTML kode og ganske få instruktioner til at arbejde med data vi har gjort tilgængelig til View'et
Controller er koden der bestemmer hvilket data fra vores model der skal være tilgængelig i de enkelte Views. Derudover er det her man håndterer hvad der skal ske når brugere udfører handlinger i View'et f.eks. hvad skal der ske ved tryk på en bestemt knap.
Castle MonoRail
Castle MonoRail er en implementation af MVC til .NET frameworket, det består af en del komponenter. Det giver os muligheden for at skrive Controller's i C# og det har en rigtig god integration til Castle ActiveRecord. Hvis man er vant til at arbejde med ASP.NET WebForms kan MonoRail virke noget omstændigt, men især i større projekter vil man nyde fordel af at MonoRail's opbygning tvinger dig til at have en klar ansvarsfordeling i din kode. Men som sagt, det er en stor omvæltning men du får noget af den kontrol tilbage som jeg personligt ofte mener man mister med WebForms
Projektstruktur
Som jeg har nævnt før kan jeg godt lide at man ved hvordan tingene fungerer før man benytter sig af kodegenerering og wizards, så selvom der findes
en Wizard til at starte et MonoRail projekt gør vi det i hånden her.
Vi opretter et Web Application projekt i Visual Studio, det er meget vigtigt at det ikke er et filsystem baseret Web Site projekt. Hvis du har Visual Studio 2005 er det ikke sikkert du har Web Application projekttypen, i så fald kan du hente den her I projektet starter vi med at oprette en række mapper så vi har nedenstående struktur:
Hvad de enkelte mapper skal bruges til vender vi tilbage til om lidt, der er endnu en forberedelse vi skal gøre før vi kan gå videre, vi skal tilføje nogle referencer til vores projekt som indeholder MonoRails funktionalitet:
- Castle.MonoRail.Framework.dll
- Castle.Core.dll
Valg af ViewEngine
Når vi skal skrive vores Views som indeholder formen for hvordan vores data og funktionalitet skal præsenteres bruger MonoRail en komponent kaldet en ViewEngine. Der findes flere forskellige ViewEngines i Castle projektet. Alle har de nogle fordele og ulemper som jeg ikke vil komme ind på her. Jeg vil blot vælge min favorit som er Brail, en ViewEngine der er baseret på programmeringssproget Boo. For at understøtte Brail ViewEngine skal vi tilføje referencer til dennes implementation:
- Castle.MonoRail.Views.Brail.dll
- Boo.Lang.dll
- Boo.Lang.Compiler.dll
- Boo.Lang.Parser.dll
Det kan virke lidt overvældende med alle disse referencer, men bare rolig. Hver af dem er med til at gøre dit arbejde lettere
Konfiguration
Vi skal have konfigureret vores web application projekt til at bruge MonoRail infrastrukturen, og også vores ActiveRecord konfiguration skal sættes op så MonoRail kan benytte denne til integrationen med ActiveRecord. Dette gør vi ved at tilføje nogle konfigurationssektioner til vores web.config
<configSections>
<section
name="monorail"
type="Castle.MonoRail.Framework.Configuration.MonoRailSectionHandler, Castle.MonoRail.Framework"
/>
<section
name="activerecord"
type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord"
/>
</configSections>
Med ovenstående har vi specificeret at der i vores konfiguration skal være en section med navnet monorail, den kan vi tilføje og konfigurere MonoRail
<monorail>
<controllers>
<assembly>Intellect.Blog</assembly>
</controllers>
<viewEngines viewPathRoot="Views">
<add type="Castle.MonoRail.Views.Brail.BooViewEngine, Castle.MonoRail.Views.Brail" />
</viewEngines>
</monorail>
Og desuden skal der være en sektion med navnet activerecord. Denne sektion er istedet for xml konfigurationen vi brugte i det tidligere indlæg om ActiveRecord så egenskaberne er de samme:
<activerecord isWeb="true">
<config>
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" />
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
<add key="hibernate.connection.connection_string" value="Data Source=.;Initial Catalog=test;Integrated Security=SSPI" />
</config>
</activerecord>
Bemærk attributten isWeb på activerecord elementet, den fortæller ActiveRecord at vi arbejder med en webapplikation dette er relevant for hvordan vores SessionScope skal opbevares, det vender vi tilbage til senere.
Ovenstående specificerer i hvilken assembly vores controllers er samt hvor vores views er og hvilken ViewEngine der skal bruges til at processere dem. Til sidst skal vi sætte vores filer op til at blive behandlet af monorail, her vælger vi en extension der bruges til at tilgå MonoRail med og vi specificerer et HttpModule:
<system.web>
<httpHandlers>
<add
verb="*"
path="*.castle"
type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory, Castle.MonoRail.Framework"
/>
</httpHandlers>
<httpModules>
<add
name="monorail"
type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework"
/>
</httpModules>
</system.web>
Bemærkt at ovenstående extension(*.castle) skal være sat op til at køre igennem ASP.NET på din webserver, hvis du ikke selv har adgang til webserveren kan du bruge f.eks. ashx som i forvejen er sat op til at blive processeret af ASP.NET
Den første Controller
En controller er blot en klasse der nedarver fra SmartDispatcherController og klassen indeholder en række offentlige metoder, disse metoder kalder vi Actions.
using Castle.MonoRail.Framework;
namespace Intellect.Blog.Contollers
{
public class PostController : SmartDispatcherController
{
public void Show(){}
}
}
Ovenstående controller er den simpleste vi kan lave, den gør ikke noget. Men lad os associere et view med den. Igen som vi så med ActiveRecord bruger MonoRail konventioner og hvis vi suffixer vores controllers klassenavne med "Controller" finder MonoRail ud af at den faktisk hedder Post. Så i vores Views mappe kan vi nu tilføje en Post mappe der skal indeholder Views til vores PostController. Og i denne mappe kan vi placere en Show.brail fil som indeholder vores viewkode.
<span style="color:red;">Hej verden!</span>
Hvis vi nu starter vores projekt og navigerer til "/Post/Show.castle" vil vores Show-action blive kaldt og vist med vores Show.brail view.
Overfør data til et View fra Controller
For at kunne præsentere vores data skal vi have en måde at tilgå dem i viewet, det er Controlleren der står for at fodre View'et med data. Det kan gøres vha. egenskaben PropertyBag på vores Controller som i nedenstående:
public void Show()
{
Post post = new Post();
post.Title = "Introduktion til MonoRail";
post.Text = "MonoRail bla bla bla ..............";
PropertyBag["Post"] = post;
}
Vi giver altså vores data et navn som vi kan bruge når vi vil tilgå det fra vores view, når vi tilgår det i viewet bruger vi følgende syntax:
<h1>${Post.Title}</h1>
<div class="PostText">
${Post.Text}
</div>
Vi kan også tilføje collections til vores PropertyBag. De kan tilgåes i viewet ved hjælp af en løkke:
<% for Post in PostCollection: %>
<h1>${Post.Title}</h1>
<div class="PostText">
${Post.Text}
</div>
<% end %>
Ovenstående løkke er en af Brail's kontrolstrukturer, Brail baserer sig på Boo så hvis du skal slå noget op kan du gøre det på f.eks. siden om løkker på Boo's hjemmeside.
Brug af værdier fra querystring i Controllers
En væsentlig ting i webudvikling er at kunne få input fra brugeren. Disse informationer kan enten komme i URL'en eller sendt til en side med POST det vil sige de er i requestets body.
MonoRail har en nem måde at håndtere dette på, vi specificerer i vores action paramtre til metoden, så lad os sige vi vil have en id parameter med til vores Show action i vores PostController:
public void Show(int id){ ... }
Hvis vi herefter navigerer vores browser til "/Post/Show.castle?id=3" vil vores parameter være populeret med værdien fra url'en. MonoRail håndterer konvertering. Dette gør det åbenlyst hvordan vi kan bruge vores ActiveRecord klasser til at hente en den angivne post op, putte den i vores PropertyBag og præsentere den på siden.
ActiveRecord samspil MonoRail
Får at få vores ActiveRecord klasser i spil tilføjer jeg dem til en mappe med navnet Model, derudover skal vi tilføje referencer til vores ActiveRecord(og nHibernate) dll'er:
- Castle.ActiveRecord.dll
- Castle.DynamicProxy.dll
- Iesi.Collections.dll
- NHibernate.dll
Derefter skal vi have håndteret vores SessionScope, når vi arbejder med webapplikationer vil man i de fleste tilfælde vælge at have et SessionScope per request, et godt sted at opbevare noget man vil bruge igennem hele requestet er i HttpContext. Men det behøves vi ikke tænke på fordi vi i vores konfiguration af ActiveRecord specificerede at vi arbejder med en webapplikation, det eneste vi skal gøre er at initialisere ActiveRecord et passende sted at gøre det er i Global.asax filen hvor vi kan eksekvere kode når vores applikation starter.
protected void Application_Start(object sender, EventArgs e)
{
Type[] types = new Type[]{typeof(Model.Blog), typeof(Model.Post), typeof(Model.Blogger), typeof(Model.Tag)};
ActiveRecordStarter.Initialize(ActiveRecordSectionHandler.Instance, types);
}
Vi konfigurerer ActiveRecord ved at hente vores ActiveRecord sektion ud fra web.config og sende en liste af de typer vi ønsker at gøre brug af i vores applikation med efterfølgende
Brug af ActiveRecord fra Controller
Med ActiveRecord konfigureret til at arbejde sammen med MonoRail kan vi nu begynde at bruge det. Jeg kan rette vores action i PostControlleren til at bruge ActiveRecord:
public void Show(int id)
{
PropertyBag["Post"] = Post.Find(id);
}
Og jeg opdaterer vores View til at se sådan her ud:
<h1>${Post.Title}</h1>
<div class="PostText">
${Post.Text}
</div>
<ul>
<% for Tag in Post.Tags: %>
<li>${Tag.Name}</li>
<% end %>
</ul>
Men faktisk kan vi få MonoRail til at gøre noget af arbejdet for os, vi kan markere en metode parameter med en attribut og få MonoRail til at hente vores instans vha. ActiveRecord.
public void Show([ARFetch("id")] Post post)
{
PropertyBag["Post"] = post;
}
Hvis vi nu åbner "/Post/Show?id=1" får vi vist vores view med data fra databasen (hvis vi har en post med id 1 selvfølgelig)
Layout på siderne
For at vi ikke skal inkludere layout for vores side i samtlige views har vi mulighed for at oprette et overordnet layout. Vi placerer dette i vores Views\Layout folder. Jeg har hentet et layout fra BlueRobot som jeg vil bruge. I layoutet placerer jeg der hvor jeg ønsker indholdet af mine views placeret et ${childOutput}. Og de steder hvor jeg skal bruge stien til roden af mit site placerer jeg ${siteRoot}.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
html>
head>
<title>Blog</title>
<style type="text/css" media="screen">@import "${siteRoot}/Content/css/layout2.css";</style>
</head>
<body>
<div id="Header"><a href="${siteRoot}">Forside</a></div>
<div id="Content">
${childOutput}
</div>
<div id="Menu">
<!-- menulinks -->
</div>
<!-- Design by bluerobot -->
</body>
</html>
For at fortælle en Controller at den skal bruge dette view som jeg har gemt i filen Default.brail, dekorerer vi vores Controller med en Layout attribut
[Layout("Default")]
public class PostController : SmartDispatcherController{...}
Det giver os et resultat som nedenstående:
Næste gang
Det var første omgang MonoRail, i næste afsnit udvider jeg vores applikation til at kunne liste poster efter deres Tags. Og derudover implementerer vi muligeden for at brugere kan kommentere på de enkelte indlæg