Blog Home  Home Feed your aggregator (RSS 2.0)  
artiso Blog - Eigene Tutorials
Neues rund um's Thema .Net
 
 Wednesday, April 01, 2009

Das Thema Test Driven Development oder auch Test First Developent gewinnt immer mehr an Beachtung. Keine Konferenz, keine Zeitschrift, kein Sprecher der was auf sich hält kommt um das Thema herum. Doch nach dem überzeugenden Vortrag sitzt man zu Hause im Büro vor einem leeren Project und wie nun anfangen? Hier scheitern bereits die ersten, weil entsprechende Publikationen oft zwar die Vorteile ausführlich schildern, aber nicht den Einstieg darstellen. Deshalb möchte ich hier einen entsprechenden Einstieg geben und mit einem wirklich leeren Projekt beginnen.

Die Theorie um TDD will ich hier einfach weglassen. Hierzu gibt es bereits Informationen genug. Und wir werden verschiedene Vereinfachung vornehmen, über die Profis etwas die Nase rümpfen werden, aber damit erhalten wir ein einfaches und praktikable Einstiegsszenario.

Zum Einsatz kommen hierbei die Testfunktionen von Visual Studio 2008 die ab der Professional Edition enthalten sind. Wir beginnen mit einer komplett leeren Solution.

image

Die Frage, die nun im Raum steht, ist: Wie schreibe ich einen Test ohne eine Methode zu haben. Ein Unit-Test besteht ja im Prinzip darin, dass wir eine Methode aufrufen und den Rückgabewert mit einem Erwartungswert vergleichen. Der Test wird aber nicht einmal kompilieren, solange die Methode nicht definiert ist. Der Workaround an dieser Stelle sieht dann oft so aus, dass man von der Methode und ihrer Klasse erst einmal einen Stub anlegt der im wesentlichen eine “ThrowNotImplemented”-Exception wirft. Damit haben wir aber eigentlich schon mehr implementiert als nach dem TDD uns lieb ist.

Ein etwas eleganterer Ansatz geht über die Definition von Interfaces. Diese Vorgehensweise eignet sich besonders gut bei einer komponentenorientierten Architektur mit einem Contract First Ansatz. Dabei werden die Schnittstellen der einzelnen Komponenten erst über Contracts (Interfaces) beschrieben bevor diese implementiert werden. Den TDD-Ablauf Rot > Grün > Refactor erweitern wir ein wenig. Damit ergibt sich folgende Abfolge:

Contract definieren > Test implementieren > Rot > Funktion implementieren > Grün > Refactor

D.h. wir erstellen in einem ersten Schritt einen Contract (genau genommen machen wir damit kein TDD sondern ein Test First. Beim TDD ist der Test das erste was erstellt werden muss, aber das ist in meinen Augen eher Haarspalterei, so funktioniert es einfach in der Praxis). Wir erstellen ein neues ClassLibrary-Projekt und erstellen dort ein Interface.

   1: namespace Contracts
   2: {
   3:     public interface IOrderCalculator
   4:     {
   5:         decimal CalculateShippinghCosts(decimal sum, decimal freeShippingMin, decimal shippingCosts);
   6:     }
   7: }

Wir wollen hier ein überschaubares, aber auch nicht zu triviales Beispiel verwenden. Die Methode CalculateShippingCosts soll zu einem gegebenen Rechnungsbetrag Versandkosten hinzuaddieren, wenn ein bestimmter Mindestbetrag nicht erreicht ist. So damit haben wir den Contract erstellt. Nun wollen wir einen Test dazu erstellen. Das geht am schnellsten durch einen Rechts-Klick auf die Methode und dann “Create Unit-Tests”.

image

Hier wird standardmäßig ein neues Test-Projekt angelegt. Darin wird ein entsprechender Unit-Test generiert.

   1: [TestMethod()]
   2: public void CalculateShippinghCostsTest()
   3: {
   4:     IOrderCalculator target = CreateIOrderCalculator(); // TODO: Initialize to an appropriate value
   5:     Decimal sum = new Decimal(); // TODO: Initialize to an appropriate value
   6:     Decimal freeShippingMin = new Decimal(); // TODO: Initialize to an appropriate value
   7:     Decimal shippingCosts = new Decimal(); // TODO: Initialize to an appropriate value
   8:     Decimal expected = new Decimal(); // TODO: Initialize to an appropriate value
   9:     Decimal actual;
  10:     actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts);
  11:     Assert.AreEqual(expected, actual);
  12:     Assert.Inconclusive("Verify the correctness of this test method.");
  13: }

Damit können wir den Test bereits zum ersten mal ausführen und er geht wie erwartet auf Rot. Aber Moment, wie funktioniert das. Wie kann der Test eine Methode auf einem Interface aufrufen? Es gibt ja noch keine Implementierung des Interfaces und ein Interface selbst lässt sich ja nicht instanziieren. Hier baut Visual Studio einen kleinen Workaround. In Zeile 4 im obigen Code sieht man, dass eine Instanz des target-Objektes über die Methode CreateIOrderCalculator() erstellt wird. Diese Methode wollen wir mal etwas genauer anschauen.

   1: internal virtual IOrderCalculator CreateIOrderCalculator()
   2: {
   3:     // TODO: Instantiate an appropriate concrete class.
   4:     IOrderCalculator target = null;
   5:     return target;
   6: }

Hier wird das Objekt einfach mit null initialisiert. Ein einfacher, aber wirkungsvoller Workaround. Damit erreichen wir unser Ziel, dass der Test kompiliert aber fehlschlägt. Nach der Implementierung ersetzen wir das null einfach durch die entsprechende Initialisierung. Damit können wir nun unsere Testcases Implementieren.

   1: [TestMethod()]
   2: public void CalculateShippinghCosts_Sum_Below_FreeShippingMin()
   3: {
   4:     IOrderCalculator target = CreateIOrderCalculator();
   5:     Decimal sum = 1;
   6:     Decimal freeShippingMin = 10;
   7:     Decimal shippingCosts = 5;
   8:     // We are below min, so we have to add shippingCosts
   9:     Decimal expected = 6;
  10:     Decimal actual;
  11:     actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts);
  12:     Assert.AreEqual(expected, actual);
  13: }
  14:  
  15: [TestMethod()]
  16: public void CalculateShippinghCosts_Sum_Above_FreeShippingMin()
  17: {
  18:     IOrderCalculator target = CreateIOrderCalculator();
  19:     Decimal sum = 20;
  20:     Decimal freeShippingMin = 10;
  21:     Decimal shippingCosts = 5;
  22:     // We are above min, so we don't add shippingCosts
  23:     Decimal expected = 20;
  24:     Decimal actual;
  25:     actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts);
  26:     Assert.AreEqual(expected, actual);
  27: }
  28:  
  29: [TestMethod()]
  30: public void CalculateShippinghCosts_Sum_Equal_FreeShippingMin()
  31: {
  32:     IOrderCalculator target = CreateIOrderCalculator();
  33:     Decimal sum = 10;
  34:     Decimal freeShippingMin = 10;
  35:     Decimal shippingCosts = 5;
  36:     // We are equal min, so we don't add shippingCosts
  37:     Decimal expected = 10;
  38:     Decimal actual;
  39:     actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts);
  40:     Assert.AreEqual(expected, actual);
  41: }

Damit haben wir unser Szenario ausreichend beschrieben. Wir können nun an die Implementierung gehen.Dazu legen wir ein neues Projekt an in dem wir eine Klasse definieren die wir von unserem Interface ableiten.

   1: namespace Components
   2: {
   3:     public class cOrderCalculator : IOrderCalculator
   4:     {
   5:         public decimal CalculateShippinghCosts(decimal sum, decimal freeShippingMin, decimal shippingCosts)
   6:         {
   7:             // If sum is greater than Min then don't add shipping costs
   8:             if (sum > freeShippingMin)
   9:                 return sum;
  10:             else
  11:                 // else add shipping costs
  12:                 return sum + shippingCosts;
  13:         }
  14:     }
  15: }

Nun müssen wir unbedingt noch daran denken, die Initialisierung des Testobjektes in unserer Testmethode anzupassen.

   1: internal virtual IOrderCalculator CreateIOrderCalculator()
   2: {
   3:     IOrderCalculator target = new cOrderCalculator();
   4:     return target;
   5: }

Nun können wir die Tests ausführen.

image

Oh, ein Test schlägt fehl. Bei genauerer Betrachtung stellen wir fest, dass wir bei der Implementierung den Fall dass die Summe gleich der Grenze ist nicht richtig berücksichtigt haben. Also hat sich hier der TDD-Ansatz schon bewährt und wir können den Fehler beheben. Damit sind alle Tests grün und wir können weiter fortfahren. Wir könnten nun z.B. auf unserem Interface weitere Methoden definieren und dafür Tests anlegen.

Also eigentlich gar nicht so schwer das mit dem TDD, oder? Freue mich auf euer Feedback.

Die Solution gibt es zum Download.

Happy Testing!

Wednesday, April 01, 2009 1:10:53 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [2]    |  |  |   | 
 Friday, March 13, 2009

MSDN Webcasts

Am 16.03.09 geht ein neuer Webcast von mir online mit dem Thema "Team Build mit Custom Build Tasks erweitern". Ich möchte hier schon mal die entsprechenden Infos veröffentlichen.

In TeamBuild lassen sich eigene Build-Task integrieren. Diese können sehr einfach erstellt werden. Hierzu wird eine Klasse erstellt und diese von Task abgeleitet. Im folgenden Beispiel wird ein Build-Task erstellt, der im Rahmen des Builds die Version der Anwendung setzt. Und zwar soll hier die Build-Nummer in der Versionsnummer der Assembly abgebildet werden. Dies bietet Vorteile, wenn zu einer bestimmten Anwendungsversion der entsprechende Build identifiziert werden soll.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using Microsoft.Build.Utilities;
   5: using Microsoft.Build.Framework;
   6:  
   7: namespace Artiso.BuildTasks
   8: {
   9:     /// <summary>
  10:     /// Creates a AssemblyVersion out of a BuildNumber
  11:     /// </summary>
  12:     /// <remarks>
  13:     /// AssemblyVersion.Minjor and AssemblyVersion.Minor will be defined fiexed in
  14:     /// the Build-Script. If the BuildNumber is Dev_Versioning_20090305.4 we use 
  15:     /// two digit year and month for AssemblyBuildNumber and day and 3 digit BuildRevision
  16:     /// for AssemblyRevision. BuildRevisio 
  17:     /// </remarks>
  18:     /// <example>
  19:     /// Dev_Versioning_20090305.4 => xx.yy.0903.05004
  20:     /// </example>
  21:     public class ExtractRevision : Task
  22:     {
  23:         #region [rgn] Fields(3)
  24:         private string buildRevision;
  25:         private string buildVersion;
  26:         private string buildNumber;
  27:         #endregion
  28:  
  29:         #region [rgn] Properties(3)
  30:         /// <summary>
  31:         /// Input Build Number
  32:         /// </summary>
  33:         [Required]
  34:         public string BuildNumber
  35:         {
  36:             set { buildNumber = value; }
  37:         }
  38:  
  39:         /// <summary>
  40:         /// Returns the sortened date of the build
  41:         /// </summary>
  42:         [Output]
  43:         public string BuildVersion
  44:         {
  45:             get { return buildVersion; }
  46:         }
  47:  
  48:         /// <summary>
  49:         /// Returns the Build Revision (number of build at this day
  50:         /// </summary>
  51:         [Output]
  52:         public string BuildRevision
  53:         {
  54:             get { return buildRevision; }
  55:         }
  56:         #endregion
  57:  
  58:         #region [rgn] Methods(1)
  59:         public override bool Execute()
  60:         {
  61:             buildVersion = "0";
  62:             buildRevision = "0";
  63:  
  64:             if (buildNumber != null && buildNumber.Contains("."))
  65:             {
  66:                 string[] buildNumberParts = buildNumber.Substring(buildNumber.LastIndexOf('_')+1).Split('.');
  67:  
  68:                 // Dev_Versioning_20090305.4 -> 0903.02005
  69:                 // use year (2 digits) and mont for buildversion
  70:                 buildVersion = buildNumberParts[0].Substring(2, 4);
  71:  
  72:                 // use day and number of build in this day for build revision
  73:                 buildRevision = buildNumberParts[0].Substring(6) + buildNumberParts[1].PadLeft(3, '0');
  74:             }
  75:             return true;
  76:         }
  77:         #endregion
  78:     }
  79: }

Das Property BuildNumber wird als Input-Parameter verwendet und mit dem Attribut [Required] versehen. Darüber kann die BuildNumber in unseren Task übergeben werden. Durch das Attribut [Output] werden die beiden Properties BuildVersion und BuildRevision als Rückgabewerte definiert. Beim Ausführen des Builds wird die Methode Execute aufgerufen. Hier werden nun aus der Build-Number die entsprechenden Informationen extrahiert und diese dann in BuildVersion und BuildRevision zurückgeschrieben. Dies ist natürlich nur ein einfaches Beispiel, aber mit diesen Grundlagen können nun beliebige Build-Tasks definiert werden. Im nächsten Schritt schauen wir uns an, wie wir diesen Custom Build Task nun in unseren Build-Prozess einbinden. Hierzu kopieren wir zunächst die Assembly in einen entsprechenden Ordner. Hier bietet sich an unter c:\Program Files\MSBuild einen entsprechenden Ordner anzulegen und dort die Assemblies abzulegen.

Nun muss das Build-Script entsprechend angepasst werden. Diese liegt in der Quellcode-Verwaltung und muss zum Bearbeiten zuerst aus- und danach wieder eingechecked werden. Um diesen Vorgang zu vereinfachen empfehle ich die TFS Sidekicks (http://www.attrice.info/downloads/index.htm) die direkt im Kontextmenü des TeamExplorers entsprechende Kommandos einfügt. Das nun ausgecheckte PROJ-File kann nun bearbeitet werden.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <!-- DO NOT EDIT the project element - the ToolsVersion specified here does not prevent the solutions 
   3:      and projects in the SolutionToBuild item group from targeting other versions of the .NET framework. 
   4:      -->
   5: <Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   6:  
   7:   <!-- Do not edit this -->
   8:   <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
   9:   <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
  10:  
  11:   <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\ArtisoBuildTasks\ArtisoBuildTasks.dll"
  12:     TaskName="ExtractRevision"/>
  13:  
  14:   <PropertyGroup>
  15:     <!-- Assembly version properties. Adjust here Major and Minor Version-->
  16:     <AssemblyMajorVersion>1</AssemblyMajorVersion>
  17:     <AssemblyMinorVersion>3</AssemblyMinorVersion>
  18:     <AssemblyBuildNumber>1</AssemblyBuildNumber>
  19:     <AssemblyRevision>1</AssemblyRevision>
  20:   </PropertyGroup>
  21:  
...
In Zeile 11 wird unser BuildTask entsprechend registriert. In Zeile 9 werden noch weitere Build-Tasks registriert. Hier gereicht es uns zum Vorteil, dass Team-Build auf MSBuild basiert. D.h. es können entsprechende Tasks für MSBuild problemlos integriert werden. Diese gibt es in großer Zahl für sehr viele Anwendungsbereiche zum großen Teil frei Verfügbar zum Download. Wir verwenden hier die MSBuild Community Tasks (http://msbuildtasks.tigris.org/). Wir werden aus diesem Paket Aktionen verwenden.
 
In den Zeilen 14 bis 20 wird eine sog. PropertyGroup angelegt. Darin werden einzelne Properties definiert und mit Default-Werten vorbelegt. Diese Properties lassen sich mit Variablen innerhalb eines Software-Codes vergleichen. Die AssemblyMajorVersion und AssemblyMinorVersion werden hier festgelegt. AssemblyBuildNumber und AssemblyRevision werden wir im weiteren Verlauf überschreiben.
 
Am Ende des Scripts direkt vor dem schließenden </Project>-Tag wird nun ein Target-Block eingefügt.
 
   1: ...
   2: <Target Name="AfterGet">
   3:    <ItemGroup>
   4:      <AssemblyInfoFiles Include="$(SolutionRoot)\**\assemblyinfo.cs" />
   5:    </ItemGroup>
   6:  
   7:    <Message Text="Get Revision Number from BuildNumber &quot;$(BuildNumber)&quot;." />
   8:    <ExtractRevision BuildNumber="$(BuildNumber)">
   9:      <Output TaskParameter="BuildRevision" 
  10:              PropertyName="AssemblyRevision" />
  11:      <Output TaskParameter="BuildVersion" 
  12:              PropertyName="AssemblyBuildNumber" />
  13:    </ExtractRevision>
  14:  
  15:    <!-- Update all the assembly info files with generated version info -->
  16:    <Message Text="Modifying AssemblyInfo files under &quot;$(SolutionRoot)&quot;." />
  17:    <Attrib Files="@(AssemblyInfoFiles)" Normal="true" />
  18:    <FileUpdate Files="@(AssemblyInfoFiles)"
  19:                Regex="AssemblyVersion\(&quot;.*&quot;\)\]" 
  20:                ReplacementText="AssemblyVersion(&quot;$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision)&quot;)]" />
  21:    <FileUpdate Files="@(AssemblyInfoFiles)"
  22:                Regex="AssemblyFileVersion\(&quot;.*&quot;\)\]" 
  23:                ReplacementText="AssemblyFileVersion(&quot;$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision)&quot;)]" />
  24:    <Message Text="AssemblyInfo files updated to version &quot;$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision)&quot;" />
  25:  </Target>
  26:  
  27: </Project>
Über den Namen des Target-Blocks mit "AfterGet" wird festgelegt, dass dieser Block ausgeführt wird, nachdem der Build-Prozess die Quelldateien aus der Versionsverwaltung geholt hat. Genau zu diesem Zeitpunkt wollen wir unsere Versionierung anpassen. In den Zeilen 3 bis 5 erstellen wir eine ItemGroup die alle assemblyinfo.cs Dateien unserer Solution enthält. In diesen Dateien wollen wir die Version anpassen. In Zeile 7 wird eine Meldung in das Build-Log geschrieben. Dies ist hilfreich, um Fehler im Ablauf des Scriptes besser einordnen zu können.
In den Zeilen 8 bis 13 wird nun unser Build-Task aufgerufen. Wir übergeben die Buildnummer $(BuildNumber) in den Parameter BuildNumber und lesen die Output-Parameter aus und schreiben diese in AssemblyRevision bzw. AssemblyBuildNumber (die Properties die wir weiter oben definiert hatten). In Zeile 17 heben wir den Schreibschutz der AssemblyInfo-Dateien auf und in den folgenden Zeilen wird mit Hilfe eines Ersetzen-Vorgangs die Version in den AssemblyInfo-Dateien ersetzt. Für diese Aktionen nutzen wir die Community Build Tasks.
Damit können wir nun die Version unserer Anwendung bei jedem Build entsprechend setzen.
In einem nächsten Schritt wollen wir den Build-Task nun noch erweitern um das Build-Result in einer ZIP-Datei zu verpacken und diese anschließend per Mail zu versenden. Auch hierbei greifen wir auf die MSBuild Community Tasks zurück. Das entsprechende Target-Tag fügen wir einfach nach dem zuvor definierten ein. Als Name geben wir "AfterCompile" an so dass diese Aktionen nach dem Kompilieren ausgeführt werden.
   1: <Target Name="AfterCompile">
   2:   <CreateItem Include="..\Binaries\Release\**\*.*" Exclude="..\Binaries\Release\**\*.pdb;..\Binaries\Release\**\*codeanalysis*">
   3:     <Output ItemName="ZipFiles" TaskParameter="Include"/>
   4:   </CreateItem>
   5:  
   6:   <Message Text="Zipping Buildresult to \\tfs\deploy\BuildDemo\BuildDemo_$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision).zip" />
   7:  
   8:   <Zip ZipFileName="\\tfs\deploy\BuildDemo\BuildDemo_$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision).zip" 
   9:        Files="@(ZipFiles)"
  10:        WorkingDirectory="..\Binaries\Release\"/>
  11:  
  12:   <Mail SmtpServer="tfs"
  13:           To="tschissler@tfs"
  14:           From="build@tfs"
  15:           Subject="BuildDemo v$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision) released"
  16:           Body="A new version of the BuildDemo was released. Please find the newest files attached to this mail. You can also download them from the download folder."
  17:           Attachments="\\tfs\deploy\BuildDemo\BuildDemo_$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision).zip"/>
  18: </Target>

Hier sammeln zunächst alle Dateien aus dem Build-Drop-Verzeichnis exklusive der PDB- und Codeanalyse-Dateien In Zeilen 8-10 werden diese Dateien in ein ZIP-File verpackt dem wir im datei-Name die Version mitgeben. Anschließend versenden wir eine e-Mail der wir dieses ZIP-File als Attachment anhängen.

Als zweite Variante wollen wir im Rahmen des Builds ein Click-Once Paket erstellen. Die Herausforderung bei der Erstellung des ClickOnce-Paketes ist dass dort die Deployment-Url hinterlegt werden muss. Vor allem wenn verschiedene Pakete für unterschiedliche Kunden erstellt werden sollen, ist dies nur durch eine Automatisierung im Rahmen des Builds sinnvoll handelbar. Hierzu ersetzen wir den AfterCompile-Target durch folgendes Script:

   1: <Target Name="AfterCompile">
   2:    <!-- Publish using ClickOnce -->
   3:    <Message Text="modify Publish directory for $(SolutionRoot)" />
   4:    <!-- Update directory where to publish the project -->
   5:    <ItemGroup>
   6:      <ProjectFiles Include="$(SolutionRoot)\Source\Dev\BuildDemo\BuildDemo.csproj" />
   7:    </ItemGroup>
   8:    <PropertyGroup>
   9:      <PublishDir>\\tfs\Deploy\BuildDemo\ClickOnce\</PublishDir>
  10:      <InstallUrl>\\tfs\Deploy\BuildDemo\ClickOnce\</InstallUrl>
  11:    </PropertyGroup>
  12:    <Attrib Files="@(ProjectFiles)" Normal="true" />
  13:    <FileUpdate Files="@(ProjectFiles)"
  14:      Regex="&lt;PublishUrl&gt;.*&lt;/PublishUrl&gt;"
  15:        ReplacementText="&lt;PublishUrl&gt;$(PublishDir)&lt;/PublishUrl&gt;" />
  16:    <FileUpdate Files="@(ProjectFiles)"
  17:        Regex="&lt;InstallUrl&gt;.*&lt;/InstallUrl&gt;"
  18:        ReplacementText="&lt;InstallUrl&gt;$(InstallURL)&lt;/InstallUrl&gt;" />
  19:  
  20:    <MSBuild Projects="@(ProjectFiles)"
  21:    Properties="PublishDir=$(PublishDir);ApplicationVersion=$(AssemblyMajorVersion).$(AssemblyMinorVersion).$(AssemblyBuildNumber).$(AssemblyRevision)"
  22:            Targets="Publish" />
  23:  
  24:  </Target>

In den Zeilen 5 bis 7 lesen wir das csproj-File der Anwendung in eine ItemGroup. Anschließend definieren wir zwei Properties für PublishDir und InstallUrl. Diese werden dann über eine Ersetzung in die csproj-Datei eingefügt. Anschließend wird ein MSBuild-Task gestartet der das ClickOnce-Paket erstellt und an der angegebenen PublishDir und mit der Versionsnummer veröffentlicht.

Das Ganze wird in dem genannten Webcast Live demonstriert. Über Feedback würde ich mich sehr freuen.

Details zur Veranstaltung: Team Build mit Custom Build Tasks erweitern [1032405249] - Microsoft Deutschland GmbH

Friday, March 13, 2009 2:05:16 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |  |   | 
 Wednesday, June 25, 2008

Auf MSDN ist nun ein Artikel erschienen den ich zusammen mit Christian Binder für das dot.net Magazin geschrieben habe. Der Artikel beschreibt, wie man mit Hilfe der TFS-API eine hierarchische Organisation von Workitems selbst erstellt. Der Artikel ist auf MSDN frei zum Download.

http://download.microsoft.com/download/4/7/a/47aca5b1-ad88-4248-949b-d0333d238516/DM_0608_Schissler_Binder.pdf

Wednesday, June 25, 2008 11:30:07 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Tuesday, April 22, 2008

MSDN Webcasts

Zusammen mit Christian Binder habe ich einen MSDN Webcast zum Thema UI-Testing mit dem UI Automation Framework erstellt. Der Webcast zeigt, wie man mit dem UI Automation Framework Anwendungen aus einer anderen Anwendung heraus steuern kann. Diese Methode eignet sich sehr gut um z.B. Unit-Tests zu erstellen, die die Oberfläche einer Anwendung testen. Damit lassen sich Oberflächentests sehr schön automatisieren. Und das beste ist, dass UI Automation Framework ist Bestandteil des .Net Framework 3.0 und damit kostenlos.

Download des Webcasts

Tuesday, April 22, 2008 10:28:00 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [2]    |  |   | 
 Wednesday, April 09, 2008

image

Ich halte momentan eine Vorleseung bei der Berufsakademie Heidenheim mit dem Titel "Implementierung verteilter Anwendungen auf Basis von Microsoft .NET". Im Zentrum dabei steht natürlich WCF. Hier nun die Folien und das WCF-Demo vom ersten Tag. Die Demos der beiden folgenden Termine werde ich hier auch noch veröffentlichen.

Folien (Powerpoint 2007)

Folien (PDF)
Code

Wednesday, April 09, 2008 1:29:33 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Thursday, February 14, 2008

Ich war am 13.02 und 14.02 auf der VSOne und habe dort insgesamt 4 Sessions gehalten. Für mich war es eine tolle Veranstaltung, die Resonanz auf meine Vorträge war sehr positiv und ich konnte viele interessante Diskussionen mit Teilnehmern und Sprecherkollegen führen.

P1000618

Hier nun meine Slides und den Code zum Download:

Session Requirement Management mit Team Foundation Server
Bei dieser Session habe ich vorgestellt, wie wir bei uns im Hause mit Hilfe des Team Foundation Servers Requirements verwalten. Hierbei kommt auch der artiso Workitem-Manager zum Einsatz um Workitems hierarchisch zu strukturieren.

Spezifikationsdokumente Verwalten
Spezifikationsdokumente werden oft als monolithische Worddokumente verwaltet Daraus ergeben sich eine Reihe von Problemen, die wir mit unserem Ansatz lösen, die Spezifikation in kleine Dokumente aufzuteilen und so jede einzelne Funktion zu Spezifizieren. Hierzu nutzen wir ein Word-AddIn das Bestandteil des artiso Workitem-Managers ist.

Mit 3 Schichten zum Erfolg
Das war der Titel meiner Architektur-Session. Hier habe ich zunächst den Aufbau und die Vorteile einer 3-Schicht-Architektur beschrieben. Anschließend erläuterte ich Komponentenorientierung. An einem kleinen Demo-Projekt zeigte ich die Planung und den Aufbau eines Projektes vom Architekturdesign bis zur Implementierung und demonstrierte dabei einige Best Practices aus unseren Projekten. Am Ende beschrieb ich noch die Auswirkungen für verteilte Systeme. Interessant für mich war, dass der Meister der Komponentenorientierung Ralf Westphal der Session beiwohnte uund den Vortrag mit interessanten Fragen bereicherte.

                   

XML-Serialisierung zur Persistierung von Objekten
XML-Serialisierung ist eine Technologie, mit der Objekte schnell und einfach in ein XML-Format überführt und auch wieder zurück konvertiert werden kann. Dies Technik ist nicht neu und viele Entwickler kennen und nutzen sie. Ich habe in der Session verschiedene Möglichkeiten aufgezeigt, wie sich XML-Serialisierung nutzen lässt, von der Persistierung von kompletten Datenobjekten über Konfigurationsdateien bis hin zur Optimierung der Speicherung von Listenobjekten in der Datenbank.

              

Nächste Woche bin ich dann auf dem Launch Event als ATE. Das wird sicher interessant bei diesem Mega-Event (6.500 Teilnehmer!).

Thursday, February 14, 2008 9:46:01 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Thursday, November 01, 2007

Ich habe mir gerade ein kleines Beispiel für den Zugriff auf einen Webservice per JavaScript per AJAX zusammengebaut. Da ich hier einige Informationen aus verschiedenen Quellen zusammengetragen habe, möchte ich das Beispiel hier kurz posten, vielleicht hilft es dem einen oder anderen, einige Informationen gesammelt zu finden.

Hier zunächst der Code für den Webservice:

   1: namespace ValuePlanner_2008.Services
   2: {
   3:     /// <summary>
   4:     /// Summary description for INNProjectExplorerWebService
   5:     /// </summary>
   6:     [WebService(Namespace = "http://tempuri.org/")]
   7:     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   8:     [GenerateScriptType(typeof(myData))]
   9:     [ToolboxItem(false)]
  10:     [ScriptService]
  11:     public class INNProjectExplorerWebService : System.Web.Services.WebService
  12:     {
  13:         [WebMethod]
  14:         [ScriptMethod]
  15:         public myData HelloWorld(int ID)
  16:         {
  17:             return new myData { ID = ID, Text = "Hello World" + ID.ToString() };
  18:         }
  19:     }
  20:     
  21:     public class myData
  22:     {
  23:         public int ID { get; set; }
  24:         public string Text { get; set; }
  25:     }
  26: }


Kurz zu erwähnen sind hier folgende Details: In Zeile 10 wird über ein entsprechndes Attribut der Webservice für die Zusammenarbeit mit AJAX konfiguriert. Das selbe gilt in Zeile 14 für die Methode, die der Webservicebereitstellt. Da die Methode einen eigenen Datentyp (myData) zurückgibt, wird in Zeile 8 dieser Typ ebenfalls für AJAX veröffentlicht. Man kan so beliebig komplexe Datenstrukturen zwichen JavaScript und dem WebService austauschen, solange diese auf Datentypen basieren, dieJavaScript auch verarbeiten kann (also z.B. keine generischen Listen etc.). Der restliche Code wird automatisch über das Webservice Template erzeugt bzw. sollte keiner weiteren Erklärung bedürfen.

In der ASPX-Seite muss ein Scriptmanager eingebaut werden und der Service darauf registriert werden. Da ich de Scriptmanager in meinem Beispiel in der Master-Page habe, verwende ich einen ScriptmanagerProxy, der es erlaubt, die Registrierungen für einzelne Seiten vorzunehmen und an den Scriptmanager in der Master-Page weiterleitet. Dies ist deshalb notwendig, da auf einer Seite nur ein einziger Scriptmanager vorkommen darf.

   1: <asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
   2:     <Services>
   3:         <asp:ServiceReference Path="~/Services/INNProjectExplorerWebService.asmx" />
   4:     </Services>
   5: </asp:ScriptManagerProxy>


Noch ein wichtiger Hinweis: Ich hatte zuerst den ToolScriptManager aus dem AJAX Component Toolkit in meiner Masterpage. Dieser scheint nicht mit dem ScriptManagerProxy zusammenzuarbeiten, jedenfalls wurde bei mir die Proxy-Klasse nicht erstellt. Erst als ich einen "normalen" ScriptManager verwendet habe, funktionierte das problemlos.

Auf dem Client wird nun eine Proxy-Klasse erstellt, die die Datentypen und die Methoden des Webservices in JavaScript nachgebildet werden. Darüber lässt sich der WebService sehr einfach aufrufen.

   1: <script language="javascript" type="text/javascript">
   2:     
   3:    function fnCallWebService()
   4:     {
   5:         ValuePlanner_2008.Services.INNProjectExplorerWebService.HelloWorld(5, fnHelloWorlSuccess, fnHelloWorlFailed);
   6:     }
   7:     
   8:     function fnHelloWorlSuccess(results, context, methodName)
   9:     {
  10:         alert(results.Text);
  11:     }
  12:     
  13:     function fnHelloWorlFailed(results, context, methodName)
  14:     {
  15:         alert ("Error calling webservice");
  16:     }
  17: </script> 


in Zeile 5 wird der WebService mit der dem Namespace und dem Namen der Webservice Klasse aufgerufen. Zunächst wird der Parameter der HelloWorld-Methode übergeben. Da der Aufruf asynchron erfolgt kann wird noch eine Methode angegeben angegeb, die im Erfolgsfall aufgerufen wird, optional noch eine Methode, wenn der Aufruf fehlschlägt. In Zeile 10 sieht man schön, dass man in JavaScript nun auch das Datenobjekt des Rückgabewertes in gewohnter Weise zugreifen kann.

Thursday, November 01, 2007 9:10:45 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Wednesday, October 24, 2007

Wer mal etwas mehr mit WCF programmiert hat, wird feststellen, dass es recht müßig ist, für jeden Service einen Host zu implementieren. Deshalb habe ich zusammen mit meinem Kollegen Tobi einen WCF Service-Managerr entwickelt. Dieser Servicemanager wird auf dem Application-Server als Windows-Dienst ausgeführt und ist in der Lage Services die in DLLs bereitgestellt werden dynamisch zu laden und auszuführen. Zur Steuerung dient ein Client der natürlich per WCF mit dem Service-Manager kommuniziert. Damit kann auch eine größere Zahl von Services auf einem Application-Server effizient verwaltet werden.

image

Der Service-Manager kann inkl. einer kleinen Doku kostenlos heruntergeladen und verwendet werden. Über ein Feedback würde ich mich freuen.

artisoServiceManager.zip (5,94 MB) Version 0.9.0
Wednesday, October 24, 2007 10:11:09 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
 Wednesday, September 20, 2006

Mit WCF (Windows Communication Foundation) lassen sich Service orientierte Architekturen (SOA) implementieren. Der Vorteil gegenüber bestehenden technologien besteht darin, dass die eigentliche Schnittstelle von dem Nachrichtenformat und dem Transportweg unabhängig ist, d.h. mann kann einfach durch Konfiguration bestimmen, ob die Kommunikation über HTTP, TCP, MSMQ, Named Pipes etc. stattfinden soll und ob der Nachrichtenaustausch über SOAP/XML, Binärserialisierung etc. erfolgen soll. WCF ist bestandteil von .net 3.0

Im Rahmen eines Projektes habe ich begonnen, mich mit der WCF zu befassen. Die ersten Ergebnisse sind in dem folgenden Tutorial beschrieben. Das Dokument soll einen einfachen Einstieg in WCF bieten.

Windows Communication Foundation Tutorial.pdf (847,8 KB)
Wednesday, September 20, 2006 9:23:36 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 
Copyright © 2010 Thomas. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.
Pick a theme: