Currently I'm working with a system handling different operations when receiving business documents in object form. Its quite obvious that the code for handling this has evolved to have a lot more responsibilities than it was intended to.
So already having an Inversion of Control container in our stack I thought we could make the operations in the pipeline configurable using the container. To achieve this we need to specify an interface for the tasks in the pipeline, in our case its pretty simple:
public interface IPipelineTask<T>
{
T Perform(T instance);
}
Secondly we need to define the pipeline itself, again using a generic interface:
public interface IPipeline<T>
{
T Execute(T instance);
}
So basically what I want to accomplish is the ability to ask my Inversion of control container for a pipeline for a certain type and be able to call the Execute method on the instance and have all associated tasks performed on the instance I pass to the method. So the code for handling an incoming object of type Invoice would look like this:
Invoice incomingInvoice = ....; ProjectContainer container = new ProjectContainer(); IPipeline<Invoice> pipeline = container.Resolve<IPipeline<Invoice>>(); Invoice processedInvoice = pipeline.Execute(incomingInvoice); container.Release(pipeline);
So lets look at the implementation of the generic pipeline that handles executing of each task:
public class Pipeline<T> : IPipeline<T>
{
private IPipelineTask<T>[] tasks;
public Pipeline(IPipelineTask<T>[] pipelinetasks)
{
tasks = pipelinetasks;
}
public T Execute(T instance)
{
T processedInstance = instance;
foreach (var task in tasks)
{
processedInstance = task.Perform(processedInstance);
}
return processedInstance;
}
}
Note the constructor takes an array of IPipelineTasks<T> this is here our Inversion of Control container comes to play and does this for us using constructor dependency injection.
Each task needs to implement the IPipelineTask<T> with its specific business object type:
public class InvoiceNotifier : IPipelineTask<Invoice>
{
private IUserNotifier usernotifier;
public InvoiceNotifier(IUserNotifier notifier)
{
usernotifier = notifier;
}
public Invoice Perform(Invoice instance)
{
usernotifier.Notify(Notification.IncomingInvoice,
"The invoice with identifier " + instance.ID + " changed state to " + instance.State);
return instance;
}
}
Normally when working with arrays in Castle Windsor we have to specify each service to be part of the array in the configuration, however in our case we want all registered implementations of the specific interface to be passed to the pipeline. To do this we can add a custom sub dependency resolver to castle, in fact we can use the ArraySubDependencyResolver already written by Castle projects founder (and soon to be Microsoftee) Hammett. With this in place we can make the following configuration and our mission is accomplished and all tasks registered in the configuration will be injected when we request the generic Pipeline of this type.
<components>
<!--Register the generic pipeline-->
<component
id="bussiness.document.pipeline"
service="Services.IPipeline`1, Services"
type="Services.Pipeline`1, Services"
lifetype="Thread"
/>
<!--Register our pipeline tasks-->
<component
id="invoice.notification"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceNotifier, Services"
lifetype="Thread"
/>
<component
id="invoice.reciever"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceReciever, Services"
lifetype="Thread"
/>
<!--Registration of services and repositories which our pipeline tasks uses-->
....
</components>
Using this infrastructure custom tasks can be specified easily in the configuration even from other namespaces and assemblies. And the pipeline doesn't need to worry about the dependencies of each Pipeline task, the container handles this
Currently I'm working with a system handling different operations when receiving business documents in object form. Its quite obvious that the code for handling this has evolved to have a lot more responsibilities than it was intended to.
So already having an Inversion of Control container in our stack I thought we could make the operations in the pipeline configurable using the container. To achieve this we need to specify an interface for the tasks in the pipeline, in our case its pretty simple:
public interface IPipelineTask<T>
{
T Perform(T instance);
}
Secondly we need to define the pipeline itself, again using a generic interface:
public interface IPipeline<T>
{
T Execute(T instance);
}
So basically what I want to accomplish is the ability to ask my Inversion of control container for a pipeline for a certain type and be able to call the Execute method on the instance and have all associated tasks performed on the instance I pass to the method. So the code for handling an incoming object of type Invoice would look like this:
Invoice incomingInvoice = ....; ProjectContainer container = new ProjectContainer(); IPipeline<Invoice> pipeline = container.Resolve<IPipeline<Invoice>>(); Invoice processedInvoice = pipeline.Execute(incomingInvoice); container.Release(pipeline);
So lets look at the implementation of the generic pipeline that handles executing of each task:
public class Pipeline<T> : IPipeline<T>
{
private IPipelineTask<T>[] tasks;
public Pipeline(IPipelineTask<T>[] pipelinetasks)
{
tasks = pipelinetasks;
}
public T Execute(T instance)
{
T processedInstance = instance;
foreach (var task in tasks)
{
processedInstance = task.Perform(processedInstance);
}
return processedInstance;
}
}
Note the constructor takes an array of IPipelineTasks<T> this is here our Inversion of Control container comes to play and does this for us using constructor dependency injection.
Each task needs to implement the IPipelineTask<T> with its specific business object type:
public class InvoiceNotifier : IPipelineTask<Invoice>
{
private IUserNotifier usernotifier;
public InvoiceNotifier(IUserNotifier notifier)
{
usernotifier = notifier;
}
public Invoice Perform(Invoice instance)
{
usernotifier.Notify(Notification.IncomingInvoice,
"The invoice with identifier " + instance.ID + " changed state to " + instance.State);
return instance;
}
}
Normally when working with arrays in Castle Windsor we have to specify each service to be part of the array in the configuration, however in our case we want all registered implementations of the specific interface to be passed to the pipeline. To do this we can add a custom sub dependency resolver to castle, in fact we can use the ArraySubDependencyResolver already written by Castle projects founder (and soon to be Microsoftee) Hammett. With this in place we can make the following configuration and our mission is accomplished and all tasks registered in the configuration will be injected when we request the generic Pipeline of this type.
<components>
<!--Register the generic pipeline-->
<component
id="bussiness.document.pipeline"
service="Services.IPipeline`1, Services"
type="Services.Pipeline`1, Services"
lifetype="Thread"
/>
<!--Register our pipeline tasks-->
<component
id="invoice.notification"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceNotifier, Services"
lifetype="Thread"
/>
<component
id="invoice.reciever"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceReciever, Services"
lifetype="Thread"
/>
<!--Registration of services and repositories which our pipeline tasks uses-->
....
</components>
Using this infrastructure custom tasks can be specified easily in the configuration even from other namespaces and assemblies. And the pipeline doesn't need to worry about the dependencies of each Pipeline task, the container handles this
Currently I'm working with a system handling different operations when receiving business documents in object form. Its quite obvious that the code for handling this has evolved to have a lot more responsibilities than it was intended to.
So already having an Inversion of Control container in our stack I thought we could make the operations in the pipeline configurable using the container. To achieve this we need to specify an interface for the tasks in the pipeline, in our case its pretty simple:
public interface IPipelineTask<T>
{
T Perform(T instance);
}
Secondly we need to define the pipeline itself, again using a generic interface:
public interface IPipeline<T>
{
T Execute(T instance);
}
So basically what I want to accomplish is the ability to ask my Inversion of control container for a pipeline for a certain type and be able to call the Execute method on the instance and have all associated tasks performed on the instance I pass to the method. So the code for handling an incoming object of type Invoice would look like this:
Invoice incomingInvoice = ....; ProjectContainer container = new ProjectContainer(); IPipeline<Invoice> pipeline = container.Resolve<IPipeline<Invoice>>(); Invoice processedInvoice = pipeline.Execute(incomingInvoice); container.Release(pipeline);
So lets look at the implementation of the generic pipeline that handles executing of each task:
public class Pipeline<T> : IPipeline<T>
{
private IPipelineTask<T>[] tasks;
public Pipeline(IPipelineTask<T>[] pipelinetasks)
{
tasks = pipelinetasks;
}
public T Execute(T instance)
{
T processedInstance = instance;
foreach (var task in tasks)
{
processedInstance = task.Perform(processedInstance);
}
return processedInstance;
}
}
Note the constructor takes an array of IPipelineTasks<T> this is here our Inversion of Control container comes to play and does this for us using constructor dependency injection.
Each task needs to implement the IPipelineTask<T> with its specific business object type:
public class InvoiceNotifier : IPipelineTask<Invoice>
{
private IUserNotifier usernotifier;
public InvoiceNotifier(IUserNotifier notifier)
{
usernotifier = notifier;
}
public Invoice Perform(Invoice instance)
{
usernotifier.Notify(Notification.IncomingInvoice,
"The invoice with identifier " + instance.ID + " changed state to " + instance.State);
return instance;
}
}
Normally when working with arrays in Castle Windsor we have to specify each service to be part of the array in the configuration, however in our case we want all registered implementations of the specific interface to be passed to the pipeline. To do this we can add a custom sub dependency resolver to castle, in fact we can use the ArraySubDependencyResolver already written by Castle projects founder (and soon to be Microsoftee) Hammett. With this in place we can make the following configuration and our mission is accomplished and all tasks registered in the configuration will be injected when we request the generic Pipeline of this type.
<components>
<!--Register the generic pipeline-->
<component
id="bussiness.document.pipeline"
service="Services.IPipeline`1, Services"
type="Services.Pipeline`1, Services"
lifetype="Thread"
/>
<!--Register our pipeline tasks-->
<component
id="invoice.notification"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceNotifier, Services"
lifetype="Thread"
/>
<component
id="invoice.reciever"
service="Services.IPipelineTask`1[[Domain.Invoice, Domain]], Services"
type="Services.InvoiceReciever, Services"
lifetype="Thread"
/>
<!--Registration of services and repositories which our pipeline tasks uses-->
....
</components>
Using this infrastructure custom tasks can be specified easily in the configuration even from other namespaces and assemblies. And the pipeline doesn't need to worry about the dependencies of each Pipeline task, the container handles this
Vi så i første indlæg hvordan vi oprettede ActiveRecord klasser. Disse klasser kunne bruges til at gemme objekter i databasen ved hjælp af metoder vi fik foræret ved at nedarve fra ActiveRecordBase<T>. I andet indlæg så vi hvordan vi kan forespørge på data.
I dette indlæg vil vi kigge på nogle lidt mere avancerede funktionaliteter ActiveRecord stiller til rådighed. Det er ikke en nødvendighed at læse dette for at komme igang med ActiveRecord men det giver en række nyttige værktøjer.
I C# har vi mulighed for at specificere datatyper, og vi får returneret fejl hvis input ikke passer ind i den specificerede datatype, men f.eks. emails og telefonnumre skal vi validere selv for at sikre de lever op til konventioner. ActiveRecord kan hjælpe os med den slags valideringer, men først skal vi tilføje en reference til den assembly der indeholder disse valideringsmetoder, nemlig Castle.Components.Validator.dll.
Når vi har refereret til den har vi en lang række validator attributter til rådighed i Castle.Components.Validator namespacet, disse kan vi nu dekorere vores egenskaber med. En af de simple validatorer hedder ValidateNonEmpty og sikrer at et felt er udfyldt, denne kan vi sætte på vores Blogger klasses Name property:
[Property, ValidateNonEmpty]
public string Name{get;set;}
For at kunne gøre brug af disse valideringer skal vores ActiveRecord implementere nogle metoder som ikke er i ActiveRecordBase<T>, men heldigvis findes der en klasse i ActiveRecord der hedder ActiveRecordValidationBase<T>. I denne klasse er der metoden IsValid og egenskaben ValidationErrorMessages. Så dem får vi på alle vores ActiveRecord's hvis vi nedarver fra denne. Det giver os nu mulighed for at skrive kode som nedenstående:
Blogger blogger = new Blogger();
blogger.Name = "";
if(blogger.IsValid())
{
blogger.Save();
}else
{
Console.WriteLine("Der var fejl i valideringen:");
foreach (string errorMessage in blogger.ValidationErrorMessages)
{
Console.WriteLine(errorMessage);
}
}
ovenstående vil give os en fejl der siger at feltet Name er krævet og skal udfyldes.
Der er selvfølgelig flere validatorer end ValidateNonEmpty. Der findes validatorer til kreditkort, email, datoer, intervaller, længder på strenge og en masse andre. Så vi kan få valideret data i vores objekter før vi gemmer dem. Hvis vi ikke er tilfredse med validatorerne kan vi specificere et regulært udtryk til en RegExValidator eller vi kan lave vores egen validator ved at implementere IValidator interfacet.
Hvis man af en eller anden grund ikke ønsker at nedarve fra en baseklasse kan man sagtens bruge ActiveRecord alligevel, alle de operationer vi har set på vores ActiveRecord klasser kan tilgåes på en anden måde:
ActiveRecordMediator<Bloogger>.FindAll(...);
Udover at der på mediator klassen er de metoder vi allerede har set, er der rent faktisk en enkelt metode der kan være en fordel at kende. Det kunne f.eks. være jeg ønskede at tælle hvor mange Post instanser der lever op til et bestemt kriterie:
int ActiveRecordPostsCount = ActiveRecordMediator<Post>.Count(Expression.Like("Title", "ActiveRecord", MatchMode.Anywhere));
Ganske som de tidligere metoder vi har set på har Count overloads der kan tage DetachedCriteria.
ActiveRecord kan en del mere end jeg har gennemgået i mine indlæg, bl.a. kan nævnes mapping af nedarvningshirakier, aggregeringsforespørgsler med HQL, hent enkelte egenskaber med HQL samt komponenter. Derudover er der et hav af muligheder for selv at ændre hvordan ting fungerer i ActiveRecord f.eks. ved at have sin egen base klasse der nedarver fra ActiveRecordBase<T> og tilgår dens protectede egenskaber.
ActiveRecord er simpelt at bruge og let at komme igang med. Samtidig med at det er simpelt giver det os mulighed for at gribe ind og bestemme hvordan ActiveRecord skal gøre stort set alle ting undervejs. Så min anbefaling vil være at komme igang, prøv at leg med det. Skriv en kommentar hvis du har problemer eller spørgsmål eller læs evt dokumentationen på Castle's hjemmeside.
Næste gang handler det ikke om ActiveRecord mere, men derimod om et andet projekt der også hører under Castle. Som på samme måde som ActiveRecord kan gøre din dataadgang let kan hjælpe dig med at gøre en anden del af din hverdag let, og så spiller det endda rigtig godt sammen med ActiveRecord. Så hæng på, vi er slet ikke færdige med Castle endnu!
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.
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 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
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:
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:
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
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
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.
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.
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.
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:
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
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)
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:
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
Lately I have been writing some posts(in Danish) introducing ActiveRecord and MonoRail. I have gotten some requests for providing a translation of these in English. This is the fist one and its heavily based on the getting started guide found on castle website.
This first post will introduce Castle ActiveRecord, an implementation of the ActiveRecord pattern on .NET. ActiveRecord is probably most known for being a part of Ruby on Rails
ActiveRecord is a way to simplify data access to a relational database in an object oriented environment. We represent database tables as classes in our object oriented language and instances of these classes represent rows in the table. In C# this will be implemented by providing static members to operate on the whole table and instance members to represent a rows data and its functionality. Below is the outline of an ActiveRecord class providing access to a database table containing information about Blogs.
public class Blog{
//Static methods that operate on the whole table
public static List<Blog> FindAll(){..}
public static Blog Find(int identifier){..}
//Instance methods that operate on a single row
public void Update(Blog instance){..}
public void Create(Blog instance){..}
//Instance properties that describe the data keept in a row
public int ID{get;set;}
public string Name{get;set;}
public string Owner{get;set;}
}
Implementing the above instance methods Update and Create is simple and in most cases trival. But each time you have to write code to adressed the following concerns:
Implementing the FindAll and Find methods seems pretty straightforward as well, but it involves some code to handle the difference between the types in the database and the types in .NET. Again this code is most often trivial.
public static List<Blog> FindAll(){
List<blog> blogs = new List<Blog>();
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["BlogEngineConnection"]);
SqlCommand comm = conn.CreateCommand("SELECT ID, Name, Owner FROM Blogs");
conn.Open();
DataReader blogReader = comm.ExecuteReader();
while(blogReader.Read()){
Blog b = new Blog();
b.ID = blogReader.GetInt32(blogReader.GetOrdinal("ID"));
b.Name = blogReader.GetString(blogReader.GetOrdinal("Name"));
b.Owner = blogReader.GetString(blogReader.GetOrdinal("Owner"));
blogs.Add(b);
}
comm.Dispose();
conn.Close();
conn.Dispose();
return blogs;
}
The above code is very simple but when working with a real world domain model we often want to have relations between our objects. With the above Blog-sample we would for instance want our Owner property to be of a complex type representing the owner instead of just a string. Another idea for improving the Blog class could be to have a collection of posts in the blog represented. All this makes the code of populating objects even more complicated, and if we add the ability to not populate all relations each time it gets even more complicated. This is where Castle ActiveRecord comes to the rescue, in return for us describing how our classes map to our database structure we get functionality to eliminate the above "not-fun-to-write" code.
As hinted above i'm about to build the data model for a blog-system. The data model we define here will be used in the next posts. If you want to code as we go along feel free to fetch the binaries of the libraries used from the Castle Projects homepage. Even though there are tools to generate our ActiveRecord classes I show you how to do this by hand as it is a firm belief of mine that its important to know what goes on before in the code before allowing tools to generate it for us.
To instruct ActiveRecord that one of our classes represent a table in our database we mark it with an attribute, this attribute is called ActiveRecord and the simple case adding it without any parameters will look like this.
[ActiveRecord]
public class Blog
{
//..
}
All projects part of the Castle Project favor convention over configuration, in this case this means that ActiveRecord will assume that our table have the same name as our class. Of course we have the ability to specify otherwise, with the ActiveRecord attribute this can be done passing the name of the table as a parameter.
[ActiveRecord("Blogs")]
public class Blog
{
//..
}
We need to add attributes to our members as well, first of all our primary key with the PrimaryKey attribute and ordinary fields with the Property attribute. Again we have the ability to specify column names as parameters or we can leave at as default. When applying this to our Blog class it will look like this.
[ActiveRecord("Blogs")]
public class Blog : ActiveRecordBase<Blog>
{
[PrimaryKey]
public int ID{
get;
set;
}
[Property]
public string Name
{
get;
set;
}
[Property]
public string Owner
{
get;
set;
}
}
If it isn't obvious from the above ID is our primary key and Name and Owner is columns in our table mapped to the properties. All attributes we have used so far is found in the Castle.ActiveRecord namespace so to compile the class above you need to import this and reference the Catle.ActiveRecord assembly.
One thing to note in the above is that i made our class inherit from ActiveRecordBase<T> this is not necessary(more about that in a future post) but it gives us a lot of functionality directly on our classes which gives us the ability to start working with ActiveRecord pretty fast
The next step is to configure the ActiveRecord framework this includes telling us which database we use and where it can be found. This configuration can be placed in an XML file(among other options). An example of an ActiveRecord configuration is found here.
<?xml version="1.0" encoding="utf-8" ?> <activerecord> <config> <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2005Dialect" /> <add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="hibernate.connection.connection_string" value="Data Source=.\SQLEXPRESS;Initial Catalog=BlogEngine;Integrated Security=SSPI" /> </config> </activerecord>
To find out more about configuring ActiveRecord you can consult the nHibernate documentation as ActiveRecord in fact uses nHibernate as its persistence layer. But to explain it short the above informs about which driver should be used for connection, which SQL-dialect should be used for generating the SQL statements, which provider should be used for serving connections and lastly the connectionstring to the database.
As stated above ActiveRecord uses nHibernate, its generally recommended to know how nHibernate works when using ActiveRecord. First of all this makes debugging easier and secondly we will need some nHibernate mechanisms to do advanced querying of our data. So the nHibernate reference manual is a valuable resource.
Now we need to initialize our ActiveRecord classes with the configuration this is done using XmlConfigurationSource residing in the Castle.ActiveRecord.Framework.Config namespace, using this we can start up the framework.
XmlConfigurationSource source = new XmlConfigurationSource("ActiveRecordConfig.xml");
ActiveRecordStarter.Initialize(source, typeof(Blog));
Its worth noticing that the Intitialize method have multiple overloads for different configuration methods and to provide more ActiveRecord types.
I haven't guided you through the creation of the tables in the database we specified in our connectionstring. That's because ActiveRecord (in most cases) can generate the schema for us based on the attributes we specified. So a call to ActiveRecordStarter.CreateSchema() after vi have initialized the framework will create your tables in the database. Similar you can drop all the elements using the DropSchema method.
With the framework initialized and the schema created we can start using our ActiveRecord classes.
Blog b = new Blog(); b.Name = "Responding to change"; b.Owner = "Jakob Andersen"; b.Create();
The Create method used above is implemented on ActiveRecordBase<T> and saves a new instance to the database, if we investigate the SQL executed against our database it will be an INSERT statement.
INSERT INTO Blogs (Name, Owner) VALUES (@p0, @p1); select SCOPE_IDENTITY(); @p0 = 'Responding to change', @p1 = 'Jakob Andersen'
So based on our Attributtes on the classes ActiveRecord generates the SQL statement above with parameters and code for retrieving the primary key after insertion. The best of all is that we can easily add new fields to our data model by just adding them to the schema and adding a property to our objects. No digging around in the code of your hand made data access layer and ensure that all places using the Blogs table is updated to include the new field.
Of course ActiveRecord can work on objects retrieved from the database, if a persistent instance needs to be updated in the database the Update method is called. An alternative to the Create and Update method is the Save method that either update or create the object based on its primary key.
As i mentioned earlier the complexity of dataaccess code becomes higher when we have complex types being properties or collections on our persistent classes. So imagine we want to model the classes with their relations as shown here.
This is easy to accomplish using ActiveRecord, we can for instance modify our Owner property on the Blog class to be of the type Blogger instead of type string. This involves changing the attribute(and type) of the property in our ActiveRecord class.
[BelongsTo]
public Blogger Owner{get;set;}
Instead of the Property attribute we not specify a BelongsTo attribute on the property, this tells ActiveRecord that the table contains a foreign-key column and that this should be used to find the Blogger object based on this key. In the other end of the relation we specify that a Blogger has many blogs using the HasMany attribute on a Blog collection, this of course means that Blogger is a primary key table being referenced by a foreign key found in the table at the other end of the relation.
[HasMany]
public IList<Blog> Blogs
{
get;
set;
}
To introduce many-to-many relations in our data model we specify that a Post object contains a list of tags, but one tag can be used for many posts. To specify this we use the HasAndBelongsToMany attribute. Because the database needs to have an extra table to contain information about this type of relation we need to specify information about this table. More specifically it needs to know the name of the foreign key for each end of the relation and the name of the table to contain these keys. Specifically when talking about our Tag-Post relation this could be specified like this.
[ActiveRecord]
public clas Tag : ActiveRecordBase<Tag>{
.....
[HasAndBelongsToMany(Table = "PostTags", ColumnKey = "TagID", ColumnRef = "PostID", Inverse = true)]
public IList<Post> Posts
{
get;
set;
}
]
public class Post : ActiveRecordBase<Post>{
....
[HasAndBelongsToMany(Table = "PostTags", ColumnKey = "PostID", ColumnRef = "TagID")]
public IList<Tag> Tags{
get;
set;
}
}
The inverse parameter to the attribute specify which end of the relation is the controlling. That is, which of the collections we will use to maintain the relationship. In our case we want to add tags to a post and not the other way around.
Having relations in our model makes it important to specify when related objects should be saved. ActiveRecord gives us this ability by providing a cascade mechanism in oru model. This means that vi can specify that an instance related to another on a property should be saved when its containing instance is persisted. The default cascade behavior is none so we should handle saving all instances manually however if we specify SaveUpdate cascade the for instance we could get a Blogger instance associated to a Blog saved when we call Save on the Blog instance.
[BelongsTo(Cascade = CascadeEnum.SaveUpdate)]
public Blogger Owner { get; set; }
The options in the CascadeEnum are Delete, All, SaveUpdate and None. As i said None is default.
One thing you will see if you download the sample code is that i have specified a length on the Text property on the Post class, this is to force the database to create a column that can contain an arbitrary long text when using CreateSchema. This could be done by specifying the StringClob or by setting the length to high number. On that note CreateSchema will specify the same length for all your string properties so if using CreateSchema this should be remembered.
This post contained a quick intro to map your domain model to your database structure, i would recommend that you download the source code and investigate it and take a look at the generated SQL using SQL profiler or configuring nHibernate to output SQL to the Console as i have done in the sample. Also investigate the methods your ActiveRecord classes have courtesy of ActiveRecordBase<T>. These methods is partly the subject of my next post
VS2008 solution: ActiveRecordBlogSample.zip
UPDATE: The complete sample beeing coded
I have had some discussions with a few people about why i would use MonoRail as it (for these people) seem like a step back from WebForms. First of all its important to understand that WebForms provide a statefull abstraction over something that is in fact stateless. In my opinion this makes something that in most cases is simple overly complicated. And secondly it often results in "funky" errors that is hard to debug because of the many steps involved before actually rendering a web-page to the user(i.e. the page lifecycle).
So to show of a single feature in MonoRail that makes a common scenario very easy i have decided to show you how to handle maintaining a many-to-many relation in a webpage. Assume that we have set up ActiveRecord classes for a Person class and this has a many-to-many relation to an Hobby class. That is one person can have multiple hobbies and different persons can have the same hobbies. To show this when editing a person on a web-page i would like to have a list of all the hobbies and the ability to check/uncheck which hobbies the person im editing has.
Using MonoRail and utilizing the available FormHelper i can get this list of checkboxes populated quite easy. Lets take a look at the Controller first:
public class PersonAdminController : ARSmartDispatcherController{
public void List(){
PropertyBag["Persons"] = Person.FindAll();
}
public void Edit([ARFetch("id")] Person person){
PropertyBag["Person"] = person;
PropertyBag["AllHobbies"] = Hobby.FindAll();
}
public void Save([ARDataBind("Person", AutoLoadBehaviour.NewInstanceIfInvalidKey)] Person person){
person.Save();
Flash["Message"] = "Person " + person.Name + " saved";
RedirectToAction("List");
}
public void Delete([ARFetch("id")] Person person){
person.Delete();
Flash["Message"] = "Person " + person.Name + " deleted";
RedirectToAction("List");
}
public void Create(){
RenderView("Edit");
}
}
So this is all needed to List, Edit, Create and Delete our persons. The List view is pretty straight forward but lets include it anyway for completeness:
${UrlHelper.Link("Create new person", {"action" : "Create"})}<br />
<table>
<% for Person in Persons: %>
<tr>
<td>${Person.Name}</td>
<td>
${UrlHelper.Link( "Edit", {"action" : "Edit", "querystring" : "id=" + Person.ID })}
</td>
<td>
${UrlHelper.Link( "Delete", {"action" : "Delete", "querystring" : "id=" + Person.ID })}
</td>
</tr>
<% end %>
</table>
So this lists the persons with their name in a tabular form and exposes our Create, Edit and Delete actions. Now lets get to te interesting part that is the Edit view:
<form action="Save.castle" method="post">
Name:<br />
${FormHelper.TextField("Post.Title", {"class" : "AdminPostTitle"})}<br />
<-- Other fields -->
Hobbies:<br />
<%
Items = FormHelper.CreateCheckboxList("Person.Hobbies", AllHobbies, {"value" : "ID", "text" : "Name"})
for Item in Items:
output Items.Item()
output Item.Name
end
%>
<-- Submit button etc -->
</form>
This is the interesting part, by naming our CheckboxList "Person.Hobbies" which corresponds to the name of the collection of hobbies on our person MonoRail will automatically check the hobbies associated with this person. And when posting the form the selected hobbies will be bound to the Person parameter in our Save action. So with the above code we have editing of many to many relations.
Using WebForms the above would probably involve manually adding each item to a checkboxlist while comparing with the existing values. Or iterating over the items in the checkboxlist after databinding to set if they should be checked or unchecked. And the code for handling this when saving in WebForms isn't trivial as well.
If you would like to prove me wrong i welcome you to provide your WebForms code for handling the above scenario.
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.
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 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
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:
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:
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
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
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.
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.
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.
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:
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
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)
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:
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
Vi så i første indlæg hvordan vi oprettede ActiveRecord klasser. Disse klasser kunne bruges til at gemme objekter i databasen ved hjælp af metoder vi fik foræret ved at nedarve fra ActiveRecordBase<T>. I andet indlæg så vi hvordan vi kan forespørge på data.
I dette indlæg vil vi kigge på nogle lidt mere avancerede funktionaliteter ActiveRecord stiller til rådighed. Det er ikke en nødvendighed at læse dette for at komme igang med ActiveRecord men det giver en række nyttige værktøjer.
I C# har vi mulighed for at specificere datatyper, og vi får returneret fejl hvis input ikke passer ind i den specificerede datatype, men f.eks. emails og telefonnumre skal vi validere selv for at sikre de lever op til konventioner. ActiveRecord kan hjælpe os med den slags valideringer, men først skal vi tilføje en reference til den assembly der indeholder disse valideringsmetoder, nemlig Castle.Components.Validator.dll.
Når vi har refereret til den har vi en lang række validator attributter til rådighed i Castle.Components.Validator namespacet, disse kan vi nu dekorere vores egenskaber med. En af de simple validatorer hedder ValidateNonEmpty og sikrer at et felt er udfyldt, denne kan vi sætte på vores Blogger klasses Name property:
[Property, ValidateNonEmpty]
public string Name{get;set;}
For at kunne gøre brug af disse valideringer skal vores ActiveRecord implementere nogle metoder som ikke er i ActiveRecordBase<T>, men heldigvis findes der en klasse i ActiveRecord der hedder ActiveRecordValidationBase<T>. I denne klasse er der metoden IsValid og egenskaben ValidationErrorMessages. Så dem får vi på alle vores ActiveRecord's hvis vi nedarver fra denne. Det giver os nu mulighed for at skrive kode som nedenstående:
Blogger blogger = new Blogger();
blogger.Name = "";
if(blogger.IsValid())
{
blogger.Save();
}else
{
Console.WriteLine("Der var fejl i valideringen:");
foreach (string errorMessage in blogger.ValidationErrorMessages)
{
Console.WriteLine(errorMessage);
}
}
ovenstående vil give os en fejl der siger at feltet Name er krævet og skal udfyldes.
Der er selvfølgelig flere validatorer end ValidateNonEmpty. Der findes validatorer til kreditkort, email, datoer, intervaller, længder på strenge og en masse andre. Så vi kan få valideret data i vores objekter før vi gemmer dem. Hvis vi ikke er tilfredse med validatorerne kan vi specificere et regulært udtryk til en RegExValidator eller vi kan lave vores egen validator ved at implementere IValidator interfacet.
Hvis man af en eller anden grund ikke ønsker at nedarve fra en baseklasse kan man sagtens bruge ActiveRecord alligevel, alle de operationer vi har set på vores ActiveRecord klasser kan tilgåes på en anden måde:
ActiveRecordMediator<Bloogger>.FindAll(...);
Udover at der på mediator klassen er de metoder vi allerede har set, er der rent faktisk en enkelt metode der kan være en fordel at kende. Det kunne f.eks. være jeg ønskede at tælle hvor mange Post instanser der lever op til et bestemt kriterie:
int ActiveRecordPostsCount = ActiveRecordMediator<Post>.Count(Expression.Like("Title", "ActiveRecord", MatchMode.Anywhere));
Ganske som de tidligere metoder vi har set på har Count overloads der kan tage DetachedCriteria.
ActiveRecord kan en del mere end jeg har gennemgået i mine indlæg, bl.a. kan nævnes mapping af nedarvningshirakier, aggregeringsforespørgsler med HQL, hent enkelte egenskaber med HQL samt komponenter. Derudover er der et hav af muligheder for selv at ændre hvordan ting fungerer i ActiveRecord f.eks. ved at have sin egen base klasse der nedarver fra ActiveRecordBase<T> og tilgår dens protectede egenskaber.
ActiveRecord er simpelt at bruge og let at komme igang med. Samtidig med at det er simpelt giver det os mulighed for at gribe ind og bestemme hvordan ActiveRecord skal gøre stort set alle ting undervejs. Så min anbefaling vil være at komme igang, prøv at leg med det. Skriv en kommentar hvis du har problemer eller spørgsmål eller læs evt dokumentationen på Castle's hjemmeside.
Næste gang handler det ikke om ActiveRecord mere, men derimod om et andet projekt der også hører under Castle. Som på samme måde som ActiveRecord kan gøre din dataadgang let kan hjælpe dig med at gøre en anden del af din hverdag let, og så spiller det endda rigtig godt sammen med ActiveRecord. Så hæng på, vi er slet ikke færdige med Castle endnu!
Vi så i første indlæg hvordan vi oprettede ActiveRecord klasser. Disse klasser kunne bruges til at gemme objekter i databasen ved hjælp af metoder vi fik foræret ved at nedarve fra ActiveRecordBase<T>. I andet indlæg så vi hvordan vi kan forespørge på data.
I dette indlæg vil vi kigge på nogle lidt mere avancerede funktionaliteter ActiveRecord stiller til rådighed. Det er ikke en nødvendighed at læse dette for at komme igang med ActiveRecord men det giver en række nyttige værktøjer.
I C# har vi mulighed for at specificere datatyper, og vi får returneret fejl hvis input ikke passer ind i den specificerede datatype, men f.eks. emails og telefonnumre skal vi validere selv for at sikre de lever op til konventioner. ActiveRecord kan hjælpe os med den slags valideringer, men først skal vi tilføje en reference til den assembly der indeholder disse valideringsmetoder, nemlig Castle.Components.Validator.dll.
Når vi har refereret til den har vi en lang række validator attributter til rådighed i Castle.Components.Validator namespacet, disse kan vi nu dekorere vores egenskaber med. En af de simple validatorer hedder ValidateNonEmpty og sikrer at et felt er udfyldt, denne kan vi sætte på vores Blogger klasses Name property:
[Property, ValidateNonEmpty]
public string Name{get;set;}
For at kunne gøre brug af disse valideringer skal vores ActiveRecord implementere nogle metoder som ikke er i ActiveRecordBase<T>, men heldigvis findes der en klasse i ActiveRecord der hedder ActiveRecordValidationBase<T>. I denne klasse er der metoden IsValid og egenskaben ValidationErrorMessages. Så dem får vi på alle vores ActiveRecord's hvis vi nedarver fra denne. Det giver os nu mulighed for at skrive kode som nedenstående:
Blogger blogger = new Blogger();
blogger.Name = "";
if(blogger.IsValid())
{
blogger.Save();
}else
{
Console.WriteLine("Der var fejl i valideringen:");
foreach (string errorMessage in blogger.ValidationErrorMessages)
{
Console.WriteLine(errorMessage);
}
}
ovenstående vil give os en fejl der siger at feltet Name er krævet og skal udfyldes.
Der er selvfølgelig flere validatorer end ValidateNonEmpty. Der findes validatorer til kreditkort, email, datoer, intervaller, længder på strenge og en masse andre. Så vi kan få valideret data i vores objekter før vi gemmer dem. Hvis vi ikke er tilfredse med validatorerne kan vi specificere et regulært udtryk til en RegExValidator eller vi kan lave vores egen validator ved at implementere IValidator interfacet.
Hvis man af en eller anden grund ikke ønsker at nedarve fra en baseklasse kan man sagtens bruge ActiveRecord alligevel, alle de operationer vi har set på vores ActiveRecord klasser kan tilgåes på en anden måde:
ActiveRecordMediator<Bloogger>.FindAll(...);
Udover at der på mediator klassen er de metoder vi allerede har set, er der rent faktisk en enkelt metode der kan være en fordel at kende. Det kunne f.eks. være jeg ønskede at tælle hvor mange Post instanser der lever op til et bestemt kriterie:
int ActiveRecordPostsCount = ActiveRecordMediator<Post>.Count(Expression.Like("Title", "ActiveRecord", MatchMode.Anywhere));
Ganske som de tidligere metoder vi har set på har Count overloads der kan tage DetachedCriteria.
ActiveRecord kan en del mere end jeg har gennemgået i mine indlæg, bl.a. kan nævnes mapping af nedarvningshirakier, aggregeringsforespørgsler med HQL, hent enkelte egenskaber med HQL samt komponenter. Derudover er der et hav af muligheder for selv at ændre hvordan ting fungerer i ActiveRecord f.eks. ved at have sin egen base klasse der nedarver fra ActiveRecordBase<T> og tilgår dens protectede egenskaber.
ActiveRecord er simpelt at bruge og let at komme igang med. Samtidig med at det er simpelt giver det os mulighed for at gribe ind og bestemme hvordan ActiveRecord skal gøre stort set alle ting undervejs. Så min anbefaling vil være at komme igang, prøv at leg med det. Skriv en kommentar hvis du har problemer eller spørgsmål eller læs evt dokumentationen på Castle's hjemmeside.
Næste gang handler det ikke om ActiveRecord mere, men derimod om et andet projekt der også hører under Castle. Som på samme måde som ActiveRecord kan gøre din dataadgang let kan hjælpe dig med at gøre en anden del af din hverdag let, og så spiller det endda rigtig godt sammen med ActiveRecord. Så hæng på, vi er slet ikke færdige med Castle endnu!
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.
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 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
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:
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:
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
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
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.
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.
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.
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:
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
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)
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