Jeg bruger ofter ViewModel-first metoden når jeg anvender MVVM, dvs. jeg opretter først ViewModel instanser i koden og lader WPF om at finde de rette views for hver type af view model.

WPF har en DataTemplate som giver mulighed for at knytte views til mine view models:

<DataTemplate DataType="{x:Type vm:AlphaViewModel}">
  <view:AlphaView />
</DataTemplate> 

Lad os for eksemplet skyld antage at vi har en abstrakt GreekViewModel og et antal view models som nedarver fra denne, f.eks. AlphaViewModel, BetaViewModel, GammaViewModel, etc. Til hver view model har vi så et view, f.eks. AlphaView, BetaView, GammaView, etc. For hver view model anvender vi en DataTemplate for at knytte view model og view sammen. Lad os for ekspemplets skyld binde vores græske herligheder til en listbox.

<ListBox ItemsSource={Binding Greeks} />

Antallet af DataTemplates stiger hver gang vi tilføjer en ny view model, men for folk med god ordenssand (eller OCD) er der heldivis en nemmere måde at knytte view model og views sammen. 

Hvis man skeler til MVC kan man forstille sig at man kan udnytte en struktur som denne:

Hvis en view model, f.eks. AlphaViewModel, ligger i ViewModels folderen, så kan jeg forvente at et tilsvarende view, f.eks. AlphaView, findes i View folderen. For at få denne adfær kan vi benytte DataTemplateSelector. Det giver os mulighed for vælge en DataTemplate til hver view model i kode. 

public class GreekDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var viewModelTypeName = item.GetType().FullName;
            var viewTypeName = viewModelTypeName.Replace("ViewModel", "View");
            var viewType = Type.GetType(viewTypeName);
            var template = new DataTemplate();
            template.VisualTree = new FrameworkElementFactory(viewType);
            return template;
        }
    }

GreekDataTemplateSelector ser på typen af item (i vores tilfælde en instans af en view model) og gætter på et navn for typen af viewet. I eksemplet vælger vi blot at erstattet ViewModel med View, dermed bliver "ViewModels.AlphaViewModel" til "View.AlphaView". Klassen FrameworkElementFactory bruges til at knytte view typen til en DataTemplate. Eksemplet kan nemt udvides, f.eks. skal der sker noget hvis et view ikke findes. Man kan søge efter flere forskellige navne, eller at der vælges et default view. Under alle omstændigheder skal koden opdateres med fejlcheck inden du sætter den i produktion.

Vores ListBox skal også lige opdateres for at få den ønskede effekt:

 

<ListBox ItemsSource={Binding Greeks} ItemTemplateSelector="{StaticResource GreekViewSelector}" />

 

 

Husk lige at oprette en instance af GreekDataTemplateSelector i Resources. DataTemplateSelector kan benyttes på mange forskellige klasser og giver mulighed for at udnytte hvordan koden er fysisk struktureret. 

 

Hvis du har en masse test data i en Excel fil er der ingen grund til at kopiere det ind i dine tests - xUnit extensions kan ordne det for dig. Det er endda på en mege elegant måde.

Jeg har noget test data fra et gammel system som indeholder nogle tal for forskellige aldre. F.eks.

Age, Gamma, Sigma, Lambda, S

Jeg har 130 aldre i skridt på et kvart år. Hvert af de øvrige koloner svarer til det ønskede resultat af en funktion med samme navn givet alderen. Der findes altså funktioner Gamma(age), Sigma(age), Lambda(age) og S(age) på min klasse. Jeg vil gerne teste disse funktioner i hver deres test metode - ikke noget med at teste alle mulige funtioner i samme test metode (fy fy).

Det første du skal gøre er at finde dit test data frem i Excel og navngive området - inkl. kolonenavne. Enten ved at du bruger navneboksen til venstre for formellinjen eller "Name Manager" under "Formulas" (Du skal bruge "Name Manager" hvis du vil ændre eller slette navne). Dernæst gemmer du dit test data i Excel 97-2003 format (*.xls). Jeg har ikke fået det til at virke med det nye fancy pancy format.

Nu skriver du dine test metoder og klistrer de magiske attributer på:

[Theory]
[ExcelData("MyData.xls", "SELECT Age, Gamma FROM TestArea51")]
public void Gamma_Test(double age, double expected)
{
  // TODO: Place tests here (sorry about the methodname)
  var actual = ...

  Assert.Equal(expected, actual);  
}

Begge attributer kræver Xunit.Extensions som har sin egen Nuget pakke (og namespace med samme navn).

Det lille fancy SQL statement er muligt fordi vi har valgt at kolonenavne skal indgå i TestArea51 området. Så slipper vi for at ændre vores test hvis vi senere ønsker at udvidere området med mere test data. Bemærk at Excel navne svarer til et område inkl. arknavnet. Man kan altså ikke have samme område navn på forskellige ark - tilgengæld skal man heller ikke angive arknavnet i attributten ovenfor.

Når du kører testen vil den blive kaldt for hver række i dit test data - også selvom nogle rækker skulle fejle. Input data fremgår i test rapport hvis det fejler, så det er nemt at finde de rækker som ikke lever op til dine krav. Vær opmærksom på at GUI test runneren godt kan kløjs lidt i det hvis alt for mange rækker fejler. Jeg har med et snuptag fået 2000+ test bare på dette data - det er ret meget output hvis alle tests fejler.

På en tidligere version af dotninjas.dk kunne man finde en artikel om hvordan man kompilerer et udtryk til funktioner som kan bruges som plugin i egne beregninger - ved at benytte den indbyggede C# compiler. Jeg vil forsøge at genopfriske lidt for artiklen, men vil denne gang compile Lambda udtryk som f.eks.

   1:  x => x * 2;

Vores specialiserede interface erstattes nu af Expression<T> (i dette tilfælde Expression<Func<double, double>>). Dette er naturligvis intressant hvis man ønsker at arbejde videre med expression træet, f.eks. hvis man ønsker at differentiere udtrykket. Hvis man blot ønsker at beregne funktionsværdier anbefaler jeg at man returnerer Func<double, double> delegaten direkte, idet der skal kaldes Compile på et lambda udtryk for at få delegaten vi skal evaluere med - man kompiler altså i to omgange.

Grundlæggende er det dette stykke kode vi ønsker at compile runtime:

   1:  using System; 
   2:  using System.Linq.Expressions; 
   3:   
   4:  public static class LambdaParser 
   5:  { 
   6:    public static Expression<Func<double,double>> Parse() 
   7:    { 
   8:      return {expression}; 
   9:    } 
  10:  } 

Vi skal blot erstatte {expression} med det ønskede udtryk som skal være en funktion med et enkelt argument af type double og return værdi double, eller f:R->R som man siger på matematisk. Man kunne også lægge sig fast på at variablen hed x og erstatte {expression}; med x=>{expression};

Jeg har valgt C# men man kan også vælge VB eller et andet sprog hvis blot man har en passende CodeDomProvider.

   1:  CSharpCodeProvider provider = new CSharpCodeProvider(); 

Brug evt. en Dictionary med CompilerVersion="v3.5" hvis du har behov for en specific version, ellers brug config filen. Ellers skal vi blot definere vores referencer og kompile koden:

   1:  CompilerParameters cp = new CompilerParameters(); 
   2:  cp.GenerateInMemory = true; 
   3:  cp.ReferencedAssemblies.Add("System.Core.dll"); 
   4:   
   5:  CompilerResults cr = provider.CompileAssemblyFromSource(cp, code); 

Husk at checke for kompilefejl (se cr.Errors.HasErros), for der bliver ikke kastet en exception. Herfra er der 3 linjer kode til vi står med vores lambda udtryk:

   1:  Type parserType = cr.CompiledAssembly.GetType("LambdaParser"); 
   2:  MethodInfo mi = parserType.GetMethod("Parse"); 
   3:  var f = (Expression<Func<double,double>>)mi.Invoke(null, new object[] { })); 

Jeg lader det være op til læseren at indkapsle koden i en genbrugelig klasse og tilføje bells and whistles. Ekstra referencer vil være en oplagt mulighed for udvidelser. En anden oplagt mulighed er at indføre en potens funktion hvad man skal bruge matematiske formler. Det kræver dog at man parser udtrykket lidt mere end blot en søg og erstat, f.eks.

   1:  x+x^2 -> x + Math.Pow(x, 2) 

Her skal Math.Pow funktionen indsættes efter plus tegnet, mens den skal indsættes efter parentens i dette udtryk

   1:  (x+1)^2 -> Math.Pow(x+1,2) 

Heldigvis skal de øvrige operatorer behandles ens, så det kun er parenteser vis skal ta' højde for.

   1:  public static string ReplacePower(string s) 
   2:  { 
   3:    int powerIndex = s.IndexOf('^'); 
   4:    if (powerIndex > 0) 
   5:    { 
   6:      string right = s.Substring(powerIndex + 1); 
   7:      string left = s.Substring(0, powerIndex); 
   8:   
   9:      int leftStart; 
  10:      int rightEnd; 
  11:   
  12:      if (left[left.Length - 1] == ')') 
  13:      { 
  14:        leftStart = FindStartParenthesis(left) - 1; 
  15:      } 
  16:      else 
  17:      { 
  18:        leftStart = left.LastIndexOfAny(opChars); 
  19:      } 
  20:      if (right[0] == '(') 
  21:      { 
  22:        rightEnd = FindEndParenthesis(right); 
  23:      } 
  24:      else 
  25:      { 
  26:        rightEnd = right.LastIndexOfAny(opChars); 
  27:      } 
  28:   
  29:      string s0 = left.Substring(0, leftStart + 1); 
  30:      string s1 = left.Substring(leftStart + 1); 
  31:      string s2 = rightEnd >= 0 ? right.Substring(0, rightEnd) : right; 
  32:      string s3 = rightEnd >= 0 ? right.Substring(rightEnd) : string.Empty; 
  33:   
  34:      return ReplacePower(string.Format("{0}Math.Pow({1},{2}){3}", s0, s1, s2, s3)); 
  35:    } 
  36:    return s; 
  37:  } 
  38:   
  39:  private static char[] opChars = new char[] { '+', '-', '/', '*', ' ', ',' }; 
  40:   
  41:  private static int FindStartParenthesis(string s) 
  42:  { 
  43:    int index = s.Length - 1; 
  44:    int parCount = 0; 
  45:   
  46:    while (index >= 0) 
  47:    { 
  48:      if (s[index] == ')') parCount++; 
  49:      if (s[index] == '(') parCount--; 
  50:      if (parCount == 0) return index; 
  51:      index--; 
  52:    } 
  53:    return index; 
  54:  } 
  55:   
  56:  private static int FindEndParenthesis(string s) 
  57:  { 
  58:    int index = 0; 
  59:    int parCount = 0; 
  60:   
  61:    while (index < s.Length) 
  62:    { 
  63:      if (s[index] == ')') parCount--; 
  64:      if (s[index] == '(') parCount++; 
  65:      if (parCount == 0) return index; 
  66:      index++; 
  67:    } 
  68:    return index; 
  69:  } 

Med FindEndParenthesis metoden skulle det også være nemt at erstatte diverse matematisk funktioner
med deres System.Math ekvivalenter som cos -> Math.Cos og sin -> Math.Sin.

Det var lide kode til at komme igang med din egen runtime expression parser - uden at der er nogen garanti for at det virker. Husk i det mindste at indsætte diverse checks så det ikke brager ned midt i en vigtig beregning.

Her i sommervarmen var det måske på tide at lege lidt med lambda udtryk: 

Expression<Func<double,double>> f = x => x + 1;

Hvis vi ser på lambda udtryk med matematiske briller vil vi ofte gerne differentiere udtrykket for f.eks. at finde maksimum eller nulpunkter. Differentialregning er heldigvis noget med en masse regler og så hårdt arbejde for resten - hvilket jo er computerens speciale. Men hvor nemt er det egentligt at differentiere et lambda udtryk?

Ovenstående udtryk parses til en Expression-træstruktur, som er et standard abstrakt syntaks træ. De enkelte noder i træer repræsenterer operatorer (plus, minus, gange og dividere, men også større end og mindre end), tal (konstanter) og symboler (også kaldet parametre som x ovenfor). Der er en helt række af expression typer (se ExpressionType for komplet liste), men noget kortere liste at klasser som nedarver Expression.

Mange ExpressionType værdier bruger f.eks. BinaryExpression - det gælder bl.a. Add, Subtract, Multiply og Divide, men også diverse boolske operatorer som f.eks. LessThan og GreaterThan (bemærk dog at disse returner bool og ikke double).

Expression finder du i System.Linq.Expression men benyttes også af Dynamic Language Runtime (DLR). Det er specielt udtryk til at styre flow i et program og tildeling af værdier som benyttes af DLR, men det vil vi ignorere i et matematisk setup.

Vi vælger at implementere Derive som en extension metode:

public static Expression Derive(this Expression e, string parameterName)
{
  switch (e.NodeType)
  {
    case ExpressionType.Add:
      ...
      break;
    
    ...
  }
}

Vi skal naturligvis implementere regler for alle værdier ExpressionType som giver matematisk mening, men først lige en metode mere.

public static Expression Derive(this Expression e, string parameterName)
{
  return Expression.Lambda(e.Body.Derive(parameterName), e.Parameters);
}

Denne metode sikrer at vi kan benytte Derive på LambdaExpression og at resultat også bliver en LambdaExpression. Vi har altså vores afledte giver som:

Expression> df = f.Derive("x");

I vores eksempel indgår tre node typer: Parameter, Constant og Add

case ExpressionType.Parameter:
  {
    ParameterExpression pe = (ParameterExpression)e;
    return Expression.Constant(pe.Name == parametername ? 1.0 : 0.0)
  }
  
case ExpressionType.Constant:
  return Expression.Constant(0.0);
  
case ExpressionType.Add:
  {
    BinaryExpressiob be = (BinaryExpression)e;
    return Expression.Add(
      be.Left.Derive(parameterName),
      be.Right.Derive(parameterName)
    );
  }

Hvilket giver os

df = x => 1 + 0;

Bemærk, at vi arbejder med doubles hele vejen igennem, ellers fejler programmet runtime ved at der ikke findes en add-operator som tager int som det ene argument og double som det andet. Resultat fra vores Derive metode er korrekt, men ikke specielt pænt at se på og måske heller ikke optimalt rent beregningsmæssigt. Til det formål foreslår jeg endnu en ekstension metode:

public static Expression Simplify(this Expression e)
{
  ...
}

Men den øvelse gemmer vi til en anden god gang - der er endnu meget arbejde på Derive.

En anden ting man bør bemærke er at der oprettes nye Expression når man skal redigere et udtryk. Det er et underliggende princip at Expression instanserne ikke kan ændres (de er immutable), hvilket man udnytter til Multiply operatoren i Derive:

case ExpressionType.Multiply: // (fg)'=f'g+fg'
  {
    BinaryExpression be = (BinaryExpression)e;
    Expression dleft = be.Left.Derive(parameterName);
    Expression dright = be.Right.Derive(parameterName);
    return Expression.Add(
      Expression.Multiply(dleft, be.Right),
      Expression.Multiply(be.Left, dright)
    );
  }

Vi har altså genbrugt undernoderne i BinaryExpression til et nyt udtryk. Jeg vil overlade det til læseren at implementere Subtract, Divide og Negate (UnaryExpression).

For funktions kald findes typen Call og klassen MethodCallExpression som med Member (MethodInfo) giver os en reference til den funktion som skal kaldes. Det er op til os at implementere den afledte af forskellige kendte funktioner, f.eks.

(Math.Sin)':
  return Expression.Call(null, typeof(Math).GetMethod("Cos"), me.Arguments);
(Math.Cos)':
  return Expression.Negate(Expression.Call(null, typeof(Math).GetMethod("Sin"),   me.Arguments);

hvor me er vores originale MethodCallExpression og me.Arguments således er de originale argumenter til funktionskaldet - jeg lader det være op til læseren at implementere kædereglen ((f(g(x)))'=f'(g(x))*g'(x)).

Endnu en stor ting fra en matematisk vinkel er at implementere potensreglen. C# har ikke nogen potens operator (^ eller ** i andre sprog), så vi skal se på ExpressionType.Call hvor metoden er Math.Pow. Den generelle potens regel er (f^g)'=f^g*(g'ln f+(g/f)*f'), men hvis g er et tal så er det den noget simplere udgave (x^n)'=n*x^(n-1):

Expression e1 = Expression.Multiply(
  me.Arguments[1], // n
  Expression.Power(
    me.Arguments[0], // x
    Expression.Subtract(me.Arguments[1], Expression.Constant(1.0)) // -1
    )
  );

dertil kommer så kædereglen:

return Expression.Multiply(e1, me.Arguments[0].Derive(parameterName));

Bemærk, at Expression.Power(...) funktionen giver os ExpressionType.Power, så hvis resultat skal differentieres igen, så skal vi også tage højde for det. Alternativt kan man benytte Expression.Call med metoden me.Method.

Det skulle være lidt til at komme igang med noget differentialregning. Heldigvis skal man "bare" følge nogle matematiske regler for at få resultatet - som måske kræver en del simplifisering for at se pænt ud, men kompileren dømmer os ikke på skønhed.

Så er det igen tid til at komme ud af byen. Påsken er en god anledning til at finde dit kamera frem og ta' med dotninja'erne ud i Dyrehaven

Torsdag d. 1. april 2010 (Skærtorsdag)

Alle er (som altid) velkomne til at joine med deres ynglings kamera (eller bare et de har lånt), det er ikke en aprilsnar (Aprilsnar vil generelt være bandlyst under hele arrangementet). For de interesserede kan der blive tale om at returnere til Frederiksberg for et måltid mad efter arrangement.

Skriv en kommentar om du kommer og om du vil være interesseret i mad. Mødested/tid aftaler vi nærmere når vi nærmer os.

Indspireret af en artikel om Effect Sketches, og med hjælp fra Reflector og Graphviz, har jeg forsøgt at få et overblik over min kode. Spørgsmålet er så om det lykkedes, her er en klasse:

Kan man gætte hvad det skal forestillet? Evt. med inspiration i artiklen? Efter at jeg har set på det et stykke tid giver det god mening for mig. Jeg kan f.eks. se at yearFraction ikke er helt færdig implementeret -- men sourcekoden er også taget fra beta.

I forbindelse med opgraderingen af Dotninjas har kommentarerne brugt deres ninja-skill til at forsvinde. Grundet finanskrisen kan der gå lidt tid før end vi får dem tilbage… Men der arbejdes på sagen.

Update: Kommenater skulle nu være tilbage… go nuts!

Ninjaerne tager deres gadgets (i dette tilfælde deres kamera) med til Vallø slot

Søndag den 11. Oktober 2009

Alle er velkomne til at joine. Vi går en tur, ta’r nogle billeder og måske snupper vi en bid brød. Der ville være en præmie til dagens bedste billede, men desværre har ingen af os skrevet en bog. t4rzsan besøgte slottet sidste år og tog bl.a. dette billede:

DSC_0254

Så tag med og få gode råd om hvordan du ta’r de gode billeder, eller vis os at du er meget bedre, eller fordi du trænger til at komme ud af byen og få lidt frisk luft. Det giver også mening at ta’ med selvom ud ikke har et DSLR 1000++ med SuperZoom XT Q34 med overloader. Du kan sagtens klare dig med et kompakt kamera.

Smid en kommentar hvis du vil med, så ser vi på transporten.

Dette indlæg er et afsnit i serien om Continuous Integration.

Nu er der jo ikke meget sjov ved bare at check koden ud, så lad os få bygget noget. Vi starter med at tilføje en task:

<tasks>
  <nant>
    <baseDirectory>d:\dev\framework</baseDirectory>
    <executable>d:\dev\tools\nant\nant.exe</executable>
    <buildFile>framework.build</buildFile>
    <targetList>
      <target>clean</target>
      <target>compile-tests</target>
    </targetList>
  </nant>
</tasks> 

Task elementer kan indeholde mange forskellige opgaver hvor vi her har valgt at køre et NAnt script. Man kunne vælge at bruge MSBuild til at bygge VS solutions med, men jeg flytter nogle ektra ting ned i build scriptet som også kan køres på udvikler maskiner. Mit NAnt script kalder så MSBuild scripts, så du har egentlig tre niveauer for hvor du skal bygge:
CC, NAnt, MSBuild (Tilføj selv flere efter behov). Hvilket niveau du vælger afhænger af dine ønsker, men jeg fortrækker at man har mulighed for at kontrollere både build og unit-tests inden man checker kode ind i source kontrol systemet.

Dette script bygger to target: Et clean target (så vi slipper af med gammelt bras) og et compile target som bygger unit test, men som afhænger af at frameworket et bygget. Tilføj selv flere targets efter behov.

Hvis du har installere på en frisk maskine uden Visual Studio, så løber du nok ind i problemer med NAnt. Problemer udmønter sig i brok over at installDir for net-2.0 ikke er angivet og det er selvom du tvinger den til at bruge net-3.5. For at løse det finder du din NAnt.exe.config fil og søger efter readregistry elementer. Attributen sdkInstallRoot skal rettes. I mit tilfælde (jeg bruger Windows SDK 6.1) til: SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1\WinSDKNetFxTools\InstallationFolder. Det skal lige gøres et par steder, så kører det. Dette kan dog være løst i en nye version af NAnt når du læser dette. Jeg har taget udgangspunkt i TreeSurgeon som giver dig både et buildscript og de tilhørende filer.

Når vores projekter er bygget kan vi kopiere bygget til vores filserver med denne lille build publisher:

<buildpublisher>
  <sourceDir>d:\dev\framework\build</sourceDir>
  <publishDir>\\fileserver\framework</publishDir>
</buildpublisher>

Så er der altid adgang til det nyeste build.

Når nu det hele kører så mangler vi bare den automatiske del. Til det formål har CC en række triggers hvoraf ForceBuildTrigger er default. Det mest oplagte for CI er at indsætte en IntervalTrigger i triggers blokken:

<triggers>
  <intervalTrigger name="Continuous" seconds="60" />
</triggers> 

Med denne trigger vil CC checke subversion hvert minut. Hvis der er sket ændringer vil den sætte gang i et nyt build. Et natligt rebuild kan tilføjes med en ScheduleTrigger

<scheduleTrigger time="23:30" buildCondition="ForceBuild" name="Scheduled" /> 

Bemærk at buildCondition er sat til ForceBuild mod default at være IfModificationExists. Dette kan også bruges for intervalTrigger hvis man skulle få brug for det. Der er mange muligheder for at tilpasse triggers, så f.eks. der ikke bygges på bestemte tidspunkter med FilterTrigger eller at ScheduleTrigger kun bygger på hverdage. Se mere under http://confluence.public.thoughtworks.org/display/CCNET/Trigger+Blocks

Nu er det bare at checke noget nyt kode ind og vente på at de dit byg (hvis du altså ikke vil vente til kl 23.30).

Dette indlæg er et afsnit i serien om Continuous Integration.

For at få vores Continuous Integration til at spille skal vi bruge et source kontrol system. Hvis du allerede har et source control system kan du springe de næste to afsnit over.

Som source control system har jeg valgt subversion og til serveren er valgt VisualSVN Server. VisualSVN laver også et plugin til Visual Studio, men modsat serveren, så skal du betale for det (i skrivende stund er prisen $49). Alternativt kan du bruge AnhkSVN, eller hvis du vil klare ærterne udenfor VS, så TortoiseSVN. Bemærk dog at der er forskel på SVN 1.5 og 1.6. Jeg løb ind i nogle uløselige "tree conflicts", så pas på med versionerne - Specielt hvis du får flyttet eller kopieret SVN folderen i dit checkout. Dette kan dog være rettet når du læser dette.

Efer at ha' installeret Visual SVN Server vælger du 'VisualSVN Server Manager' fra startmenuen. Højreklik på roden i træstruktion, vælg options og vælge hvor du vil ha' dit repository til at ligge. Her kan du også vælge mellem Windows og subversion authentication og hvordan du vil tilgå dit repository. Opret dit repository (gerne med den anbefalede trunk/brances/tags struktur) og evt. brugere. Der er en kort beskrivelse (om end noget længere end min) af opsætningen proceduren på VisualSVNs hjemmeside. Lav et checkout på din udvikler maskine, fyld nogle filer i og commit ændringerne.

Nu skal vi så igang med CC. Programmet kører som default ikke efter det er blevet installeret. Det skal først konfigureres og det er meget nemmere at bruge CC console programmet som der ligger et link til på dit skrivebord. Du kan sagtens starte consolen og se programmet køre. Når du opdaterer konfigurationen indlæses den nye fil automatisk. Efter at konfigurationen er kommet på plads installeres services ved at køre installutil på ccservice.exe i server folderen.

Indtil videre starter vi blot CC ved at bruge linket på skrivebordet og finder ccnet.config i server folderen (der er også en ccnet.exe.config som kan bruges til at styre f.eks. logging). Det er en simple xml fil som indeholder alle de projekter din build server skal bygge. Lad os start med et enkelt projekt og lad os gi' vores projekt et navn:

<cruisecontrol>
  <project>
    <name>Framework</name>
    <workingDirectory>d:\dev\framework</workingDirectory>
    <artifactDirectory>d:\dev\framework\artifacts</artifactDirectory>
  </project>
</cruisecontrol>

Da jeg var igang valgte jeg også lige at fortælle hvor jeg ville ha' projektet liggende, men ellers sker der ikke så meget der er værd at se. Appropos se, så kan du holde øje med projektet via dashboardet på http://bs/ccnet. Den kan melde nogle fejl, men de forsvinder efterhånden som du får konfigureret og bygget projektet. Dashboardet har også et link til CCTray som du kan installere på din udvikler maskiner og som navnet antyder viser CC status som tray icon.

Hvis du vil ha’ flere projekter tilføjer du bare yderligere projekt elementer i konfigurationsfilen. Men først må det være tid til at checke noget kode ud fra vores Subversion repository ved at tilføje et sourcecontrol element under project noden:

<sourcecontrol type="svn">
  <trunkUrl>http://bs:8080/svn/framework/trunk</trunkUrl>
  <executable>d:\Program Files\VisualSVN Server\bin\svn.exe</executable>
  <workingDirectory>d:\dev\framework</workingDirectory>
</sourcecontrol>

TrunkUrl og executable afhænger af din Subversion installation. Jeg har valgt at køre på samme server og bruge subversion authentication, men du kan gøre andre valgt. Det er også muligt at angive username og password elementer. Det er også muligt at vælge andre source kontrol systemer, men så kan der være andre detaljer som skal udfyldes.

Hvis du går ind i dashboarded og ser dit projekt kan du vælge "Force Build", hvorefter du skulle kunne se dit checkout i d:\dev\framework folderen hvis builded afsluttede succesfuldt. Build rapport giver også en oversigt over hvilke filer der er checket ind siden sidste build og hvilke beskeder der er attachet til checkins. Så ikke noget med at skrive junk beskeder når du checker ind. Hvis buildet fejlede af den ene eller anden grund (compilefejl, unit test fejl, etc.) så vi man også her kunne se hvem synderen er.

I næste indlæg skal vi se hvordan vores build rent faktisk får bygget noget.