Blue Orange Green Pink Purple

Jürgen Oberngruber's Blog

Jun 11

HowTo: Eigene Regeln für Microsoft StyleCop erstellen

Im ersten Teil der Serie habe ich erklärt, wie man StyleCop grundsätzlich in Verbindung mit MSBuild und Visual Studio verwenden kann. Heute möchte ich kurz erklären, wie man eigene Regeln für StyleCop definiert und diese mit dem Setup vom ersten Teil verwenden kann. Da ich selber ein verfechter davon bin, private Member (Properties, Klassen, Methoden, etc.) mit einem _ (Unterstrich) als Präfix zu versehen, möchte ich heute eine StyleCop Regel implementieren, die genau diesen Standard überprüft.

Eigene Regeln implementieren!
Um eigene Regeln für StyleCop zu implementieren, müssen wir zuerst einmal StyleCop herunterladen bzw. installieren, weil wir dabei 2 wichtige Assemblies bekommen, die wir für die Entwicklung benötigen. Als nächstes erstellen wir uns in Visual Studio eine leere Klassenbibliothek und referenzieren die 2 Assemblies

  • Microsoft.StyleCop.dll
  • Microsoft.StyleCop.CSharp.dll

Haben wir das, erstellen wir uns eine neue Klasse und leiten diese von Microsoft.StyleCop.SourceAnalyzer ab. Das ist die Basisklasse für alle Regeln, die in StyleCop verfügbar sein sollen. Zusätzlich definieren wir über der Klasse noch ein Attribut, das angibt, für welche Sprache die Regel verwendet werden kann: [SourceAnalyzer(typeof(CsParser))].

Die Basisklasse implementiert eine Methode namens AnalyzeDocument(…), die wir in unserer eigenen Klasse nun überschreiben sollten, um auch wirklich auf den Quellcode der einzelnen Dateien, die später von unserer Regel evaluiert werden sollen, Zugriff zu haben:


public override void AnalyzeDocument(CodeDocument document)
{
   CsDocument csdocument = document as CsDocument;
   if (csdocument == null) return;

   csdocument.WalkDocument(new CodeWalkerElementVisitor<object>(this._VisitElement));
}

Die Methode hat einen Parameter vom Type CodeDocument. Dieses Dokument können wir anschließend auf ein CsDocument casten, da wir ja wissen, dass unsere Regel nur für CSharp Dokumente verwendet werden kann. Um jetzt das Dokument durchlaufen zu können, bietet das SDK hierfür verschiedene Klassen an, mit den denen unterschiedliche Typen evaluiert werden können. Wir verwenden hier die Klasse CodeWalkerElementVisitor, weil wir nur an Elementen (Klassen, Properites, usw.) interessiert sind. Diese Klassen verwenden das Visitor Design Pattern. Das heißt, dass der Visitor das Quelldokument durchparst und sobald er auf ein (in unserem Falle) Element trifft, die Methode _VisitElement aufruft:

private bool _VisitElement(CsElement element, CsElement parentElement, object context)
{
   if (element.Generated) return true;

   if (element.AccessModifier == AccessModifierType.Public ||
       element.AccessModifier == AccessModifierType.Internal) return true;

   if (element.ElementType == ElementType.Field ||
       element.ElementType == ElementType.Accessor ||
       element.ElementType == ElementType.Constructor ||
       element.ElementType == ElementType.ConstructorInitializer ||
       element.ElementType == ElementType.Destructor ||
       element.ElementType == ElementType.EmptyElement ||
       element.ElementType == ElementType.EnumItem ||
       element.ElementType == ElementType.ExternAliasDirective ||
       element.ElementType == ElementType.File ||
       element.ElementType == ElementType.Indexer ||
       element.ElementType == ElementType.Namespace ||
       element.ElementType == ElementType.Root ||
       element.ElementType == ElementType.UsingDirective) return true;

   if (element.Declaration.Name.Trim().StartsWith("_")) return true;

   this.AddViolation(element, element.LineNumber, "NonPublicMemberUnderscorePrefix");

   return true;
}

Die Methode überprüft, ob es sich bei dem Element um einen Typ handelt, der für uns auch von Interesse ist. Falls ja, muss der Name des Elements mit einem Unterstrich beginnen. Tut es das nicht, verwenden wir die Methode AddViolation der Basisklasse, um StyleCop mitzuteilen, dass wir auf eine Regelverletzung gestossen sind. Der 3. Parameter “NonPublicMemberUnderscorePrefix” ist wichtig: Um diese Regel nun tatsächlich verwenden zu können, müssen wir sie noch mit einigen Metainformationen ausstatten:

MetaInfos für StyleCop Regeln
StyleCop verwendet für jede Regel ein XML File, das die notwendigen Metainformationen für jede Regel beinhaltet. Wir müssen daher für unsere eigene Regel noch ein XML File erstellen, dass genau diese Metainformationen beinhaltet. Zuerst fügen wir zu unserem VS Projekt ein neues XML File hinzu und ändern in den Properties die Eigenschaft Build Action auf Embedded Resource. Anschließend können wir im XML File die Metainformationen befüllen:

<?xml version="1.0" encoding="utf-8" ?>

<SourceAnalyzer Name="My Rules">
 <Description>
  My custom StyleCop rules.
 </Description>
 <Rules>
  <RuleGroup Name="Naming Rules">
   <Rule Name="NonPublicMemberUnderscorePrefix" CheckId="MY1001">
    <Context>
     NonPublic members must start with an underscore
     followed by an upper-case letter.
    </Context>
    <Description>
     NonPublic members must start with an
     underscore followed by an upper-case letter.
    </Description>
   </Rule>
  </RuleGroup>
 </Rules>
</SourceAnalyzer>

Der Aufbau dieses XMLs ist relativ einfach. Zuerst definieren wir einen neuen SourceAnalyser und geben ihm einen Namen und eine Beschreibung. Dann definieren wir eine neue Gruppe für unsere Regel (RuleGroup, wir könnten auf diese Gruppe auch verzichten). Und jetzt kommt der wirklich interessante Teil. Wir definieren unsere eigene Regel und geben ihr einen Namen: NonPublicMemberUnderscorePrefix. Dieser Name muss exakt mit dem übereinstimmen, den wir vorher bei der Methode AddViolation(…) im Sourcecode als 3. Parameter angegeben haben. Sind diese Namen unterschiedlich, kann StyleCop die Metainformationen nicht eindeutig zuordnen. WICHTIG: Der Name der XML Datei muss exakt der gleiche wie der Name der Sourcecode Datei sein. Also heißt die *.cs Datei MyRules.cs muss die XML auch MyRules.xml heißen.

Um diese Regel jetzt auch tatsächlich in StyleCop verwenden zu können, kopieren wir sie in das Installationsverzeichnis von StyleCop. Starten wir jetzt den StyleCopSettingsEditor, sollten wir unsere neue Regel bereits in der Liste sehen können:

Integration mit MSBuild
Um solche eigenen Regeln auch mit dem Setup aus dem ersten Teil verwenden zu können, müssen wir am *.targets File von StyleCop etwas verändern. Bei diesem Setup haben wir alle Dateien (*.dll und *.targets), die für StyleCop notwendig sind, in einem relativen Verzeichnispfad abgelegt. In das selbe Verzeichnis kopieren wir nun unsere soeben erstellte StyleCop-Assembly. Im selben Verzeichnis sollte eigentlich eine Datei namens Microsoft.StyleCop.Targets liegen. Die öffnen wir uns suchen nach der Zeile AdditionalAddinPaths=”…”. Der Pfad, der dort eingetragen ist, gibt an, wo zusätzliche Assemblies gespeichert sind, in denen eigene StyleCop Regeln definiert sind. Nun, da wir gerade unsere Assembly in das selbe Verzeichnis kopiert haben, können wir mit den VS Macros direkt relativ auf dieses Verzeichnis verweisen:

<StyleCopTask
   ProjectFullPath="$(MSBuildProjectFile)"
   SourceFiles="@(StyleCopFiles)"
   AdditionalAddinPaths="$(ProjectDir)\Integration\"
   ForceFullAnalysis="$(StyleCopForceFullAnalysis)"
   DefineConstants="$(DefineConstants)"
   TreatErrorsAsWarnings="$(StyleCopTreatErrorsAsWarnings)"
   CacheResults="$(StyleCopCacheResults)"
   OverrideSettingsFile="$(StyleCopOverrideSettingsFile)"
   OutputFile="$(StyleCopOutputFile)"
   MaxViolationCount="$(StyleCopMaxViolationCount)" />

Das wars. Der Sourcecode (den ich etwas erweitert habe) kann wie immer unter http://downloads.juergenoberngruber.at/blog/CustomStyleCopRules.zip herunter geladen werden.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Jun 05

HowTo: Microsoft StyleCop Integration mit Visual Studio und MSBuild

Microsoft StyleCop ist mal echt was tolles. Vielleicht kennt ja wer folgendes Szenario:

“Zu Beginn eines neuen Projektes nehmen sich alle Projektmitglieder vor, mal etwas mehr Codequalität in den Sourcecode zu bringen, weil sie bemerkt haben, wie schwierig es ist, einen Sourcecode zu verstehen, wenn er nicht aus seiner eigenen Hand kommt (bei manchen ist es sogar schwierig, den eigenen Code nach ein paar Wochen noch zu verstehen). Gut, man einigt sich darauf, ein paar Prinzipien aus der agilen Softwarentwicklung einzusetzen: Pair Programming und Code Reviews.

Anfangs funktioniert das alles wunderbar. Jeder kennt den Code des anderen, es etabliert sich ein gewisser Standard, weil jeder versucht, ähnlich zu programmieren. Im Laufe der Zeit wirds im Projekt stressig – es bleibt keine Zeit mehr, zusätzliche Arbeit zu leisten – hauptsache das Projekt wird irgendwie fertig. Dafür verzichtet das Team auf Qualität. Hauptsache in time and budget.”

Ich denke, dass viele Projektteams in der IT ähnliches schon einmal erlebt haben. Um sich einen wesentlichen Teil der Zeit von manuellen Code-Reviews zu sparen, kann man Tools wie StyleCop einsetzen. StyleCop wird als Visual Studio Addin und als MSBuild Task ausgeliefert. Einmal installiert, kann man StyleCop direkt über das Visual Studio UI starten.

Was macht StyleCop?
StyleCop ist ein Tool zur Codeanalyse von .NET Sourcecode (derzeit wird leider nur C# Code unterstützt). StyleCop analysiert tatsächlich den (unkompilierten) Sourcecode und unterscheidet sich somit von FxCop. StyleCop wird standardmäßig mit zahlreichen nützlichen Regeln geliefert, die zur Gänze auch den .NET Framework Coding Guidelines entsprechen. Das tolle an StyleCop ist die Möglichkeit, mit Hilfe dem mitgelieferten SDK eigene Regeln zu generieren und diese später mit den out-of-the-box Regeln zu kombinieren.

Lädt man sich StyleCop herunter, bekommt man ein MSI Paket, dass einerseits ein Visual Studio Addin und, wenn man es aktiviert, auch einen MSBuild Task installiert. Diesen MSBuild Task werden wir später noch benötigen.

stylecop

Nach der Installation kann man StyleCop direkt in Visual Studio ausführen:

StyleCop_1

Integration mit MSBuild
StyleCop erlaubt auch die Integration mit MSBuild. Man erreicht damit, dass sobald ein Visual Studio Projekt erstellt wird – also bei jedem kompilieren – auch StyleCop automatisch mit ausgeführt wird. Dadurch ersparen wir uns das manuelle Triggern von StyleCop. Es gibt im Internet wenige, aber dennoch einige Beschreibungen, wie StyleCop mit MSBuild integriert wird. Ich möchte hier meine eigene Interpretation vorstellen:

  1. Zuerst erstellen wir uns ein Projekt in Visual Studio und speichern es lokal ab.
  2. Dann gehen wir zum dem Verzeichnis, wo wir zuvor das Projekt gespeichert haben. Dort erstellen wir uns einen Ordner Integration.
  3. Jetzt müssen wir das Installationsverzeichnis von MSBuild die installierten StyleCop Dateien suchen. Standardmäßig sollte es unter %Program Files%\MSBuild\Microsoft\StyleCop\v4.3\
  4. Jetzt kopieren wir aus diesem Verzeichnis alle Dateien mit der Endung *.dll und *.targets in das erstellte Integation Verzeichnis im Projektordner.
  5. Wir öffnen das VS Projektfile *.csproj mit einem Texteditor und fügen folgende Zeilen hinzu: Nach
    <Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” />

    kommt folgende Zeile
    <Import Project=”$(ProjectDir)\Integration\Microsoft.StyleCop.targets” />

    und in den ersten <PropertyGroup>Knoten kommt
    <StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>

  6. Jetzt speichern wir die Datei und öffnen das Projekt erneut in Visual Studio, kompilieren und bekommen wahrscheinlich eine lange Liste an Fehlern aufgelistet.

stylecop_error

Zur Erklärung: Wir kopieren uns die *.dll und *.targets Dateien relativ in den Projektordner, um das Projekt auch verteilen zu können und nicht zwingend davon auszugehen, dass auf dem Zielrechner auch StyleCop installiert sein muss. Im Projektfile geben wir anschließend an, dass der StyleCop Task bei jedem Erstellen ausgeführt werden soll. Um auf den relativen Pfad zu verweisen (/Integration/Microsoft.StyleCop.Targets) verwenden wir die VS Macros: $(ProjectDir). Anschließend geben wir noch an, das StyleCop jede Regelverletzung als Fehler behandeln soll. Somit erreichen wir, das ein Erstellen eines Projekts nicht möglich ist, sobald mindestens eine Regel verletzt wurde. Würden wir die Regelverletzungen nur als Warnungen behandeln, erreichen wir, dass ab einem bestimmten Zeitpunkt (sobald es im Projekt stressig wird) die Entwickler die Warnungen einfach ignorieren.

Es gibt noch mehr Eigenschaften, die gestetzt werden können. Dazu einfach im .Targets File nachsehen, welche Eigenschaften noch zur Verfügung stehen.

Um jetzt gewissen Regeln zu ignorieren, gibt es bei StyleCop ein Settingsfile names Settings.StyleCop File. Das wird normalerweise nach dem ersten Durchlauf von StyleCop direkt neben dem Projektfile erstellt. Diese Datei beinhaltet Xml Informationen über alle Regeln, die für das aktuelle Projekt deaktiviert werden sollen. Man hat jetzt zwei Möglichkeiten:

  • Entweder man modifiziert diese Datei mit einem Xml Editor manuell oder
  • man verwendet das mitgelieferte Tool StyleCopSettingsEditor.exe. Dazu ist aber eine lokale StyleCop Installation notwendig.stylecop_settings

Das wars. Im nächsten Teil gehts darum, wie man solche StyleCop Regeln selber erstellen kann und ebenfalls mit der MSBuild Integration verwenden kann.

Das Beispielprojekt gibts wie immer unter: http://downloads.juergenoberngruber.at/StyleCopDemo.zip

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
May 29

HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 2

Im ersten Teil des Eintrags bin ich ja auf die Basisthemen für die Erstellung von eigenen Visual Studio Templates eingegangen. Dabei habe ich 3 konkrete Fragen offen gelassen:

  • Wie erreiche ich es, dass ich ein Template erstelle, dass mehrere Projekte beinhaltet, die mit unterschiedlichen Suffix im Projektnamen ausgestattet sein sollen?
  • Wie erreiche ich es, dass meine Templates nicht in der Kategorie “My Templates” sondern unter “Visual Studio installed templates” aufscheinen?
  • Wie kann ich meine Templates ohne Visual-Studio-Wizard modifizieren. Beispielsweise, wie kann ich das Icon, dass später neben meinem Template angezeigt wird, austauschen?

Diese Fragen möchte ich in diesem Post nun detailliert erklären.

Wie erreiche ich es, dass ich ein Template erstelle, dass mehrere Projekte beinhaltet, die mit unterschiedlichen Suffix im Projektnamen ausgestattet sein sollen?
Angenommen wir verwenden unser Projekt aus dem ersten Teil für eine Webanwendung, deren Projektstruktur folgende Gliederung hat:

  • Projektname.Web
    Beinhaltet alle spezifischen Dateien, die für die richtige Darstellung der Webseite notwendig sind (*.aspx, *.asxc, *.asmx, Bilder, Javascripts, Stylesheets, …)
  • Projektname.Core
    Beinhaltet die notwendigen MVP Klassen, Hilfsklassen, etc. Also zusammengefasst die gesamte Businesslogik.

Das heißt, wir wollen nur aus dieser Solution eine Projektvorlage erstellen, die zum einen beide Projekte erstellt und zum anderen an den Projektnamen das richtige Suffix anhängt (zB MySite.Core und MySite.Web). Das funktioniert leider mit dem Visual Studio Template Wizard nicht. Der Workflow ist trotzdem relativ einfach:

  • Zuerst erstellen wir über den Template Wizard von Visual Studio jeweils eine Projektvorlage für das Projektname.Web und für Projektname.Core. WICHTIG: Bei der Erstellung der Templates immer die Checkbox “Automatically import the template into Visual Studio” wegklicken, weil wir diese beiden Templates nicht inkludieren wollen, sondern sie später zu einem Template zusammenfassen.
  • Als nächstes extrahieren wir die erstellten *.zip Files und kopieren uns eine *.vstemplate aus einem der Folder und kopieren sie auf die Ebene, wo die beiden extrahierten Templatefolder liegen. Schlussendlich sollte man folgende Verzeichnisstruktur erhalten (die *.zip Files könnte man an dieser Stelle jetzt löschen):templates_2
  • Jetzt müssen wir die MyTemplate.vstemplate, die auf der Ebene der beiden Verzeichnisse (TemplateDemo.Core und TemplateDemo.Web) liegt, modifizieren. Warum? Wir müssen in dieser Datei angeben, welche Projekte erstellt werden sollen, sobald das Template geladen wird.  Dafür gibt es den Knoten <ProjectCollection>, in dem mehrere Projekte angegeben werden können. Eine gute Referenz findet man wie immer im MSDN: http://msdn.microsoft.com/de-de/library/31cdwx28%28VS.80%29.aspx.
    <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
     <TemplateData>
        <Name>TemplateDemo</Name>
        <Description>Creates a sample web-solution using MVP.</Description>
        <ProjectType>CSharp</ProjectType>
        <ProjectSubType></ProjectSubType>
        <SortOrder>1000</SortOrder>
        <CreateNewFolder>true</CreateNewFolder>
        <DefaultName>Template</DefaultName>
        <ProvideDefaultName>true</ProvideDefaultName>
        <LocationField>Enabled</LocationField>
        <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
        <Icon>__TemplateIcon.ico</Icon>
     </TemplateData>
     <TemplateContent>
        <ProjectCollection>
           <ProjectTemplateLink ProjectName="TemplateDemo.Web">
              TemplateDemo.Web\MyTemplate.vstemplate
           </ProjectTemplateLink>
           <ProjectTemplateLink ProjectName="TemplateDemo.Core">
              TemplateDemo.Core\MyTemplate.vstemplate
           </ProjectTemplateLink>
        </ProjectCollection>
     </TemplateContent>
    </VSTemplate>
    
  • Fertig.

Jedoch gibt es mit diesem Template noch ein Problem.  Unsere Projekte heißen nach der Erstellung immer TemplateDemo.Web und TemplateDemo.Core – egal wie die Solution genannt wird. Das ist nicht gut und wollen wir auch nicht. Die beiden Projekte sollen so heißen wie die Solution und als Suffix jeweils .Web und .Core erhalten. Hierfür gibts in der .vstemplate Datei Platzhalter, die später während der Verwendung des Templates von VS automatisch ausgetauscht werden. Eine Übersicht gibts hier http://msdn.microsoft.com/en-us/library/eehb4faa%28VS.80%29.aspx. In unserem Fall verwenden wir $projectname$:

<TemplateContent>
  <ProjectCollection>
    <ProjectTemplateLink ProjectName="$projectname$.Web">
      Web\Web.vstemplate
    </ProjectTemplateLink>
    <ProjectTemplateLink ProjectName="$projectname$.Core">
      Core\Core.vstemplate
    </ProjectTemplateLink>
  </ProjectCollection>
</TemplateContent>

Als letztes müssen wir die Verzeichnisstruktur zippen und das erzeugte *.zip File in den Templates Ordner kopieren (bei mir C:\%user_directory%\Documents\Visual Studio 2008\Templates\ProjectTemplates, dieser kann übrigens in VS eingestellt werden, verweist aber standardmäßig auf diesen Pfad).

Wie erreiche ich es, dass meine Templates nicht in der Kategorie “My Templates” sondern unter “Visual Studio installed templates” aufscheinen?
Visual Studio zeigt alle Templates, die im benutzerdefinierten Templatepfad C:\%user_directory%\Documents\Visual Studio 2008\Templates liegen, in der Kategorie “My Templates” an. Jetzt wollen wir aber, das unser eigendes Template als “Installed Template” angezeigt wird. Ganz einfach:

  • Visual Studio speichert die “installierten” Templates im Ordner %VS_InstallDir%/Common7\IDE\ProjectTemplates (bei mir C:\Users\Jürgen Oberngruber\Documents\Visual Studio 2008\Templates\ProjectTemplates). Das gleiche gilt übrigens auch für ItemTemplates (%VS_InstallDir%/Common7\IDE\ItemTemplates). Hier drinnen gibts jetzt weitere Hierachien. Wir wollen unser Projekt jetzt für die Sprache C# in einer eigenen Rubrik anzeigen. Dafür gehen wir in den Ordner C# und erstellen uns dort einen Ordner zB MVP.  In diesem Ordner kopieren wir unsere Templatedatei. Das sollte dann so aussehen:templates_2_1
  • Wenn wir jetzt Visual Studio starten, sehen wir leider noch gar nichts. Wir müssen Visual Studio noch mitteilen, seinen internen Template Cache neu zu erzeugen. Das funktioniert mit dem Befehl devenv.exe /installvstemplates. Daszu einfach ein Command Prompt (als Administrator) starten und den Befehl ausführen. Starten wir jetzt Visual Studio neu und öffnen den “Neues Projekt” Dialog, sehen wir, dass es nun eine neue Rubrik names MVP gibt und darin sich unser Template als “installed Template” befindet.templates_2_3

Wie kann ich meine Templates ohne Visual-Studio-Wizard modifizieren. Beispielsweise, wie kann ich das Icon, dass später neben meinem Template angezeigt wird, austauschen?
Hierfür müssen wir wieder unsere *.vstemplate Datei modifizieren. Visual Studio erstellt beim erstellen eines Templates eine __TemplateIcon.ico Datei, auf die im *.vstemplate referenziert wird.

Um nun dieses Icon auszutauschen können wir nun entweder diese __TemplateIcon.ico mit einer anderen Datei überschreiben oder wir erstellen uns eine neue ICO Datei mit anderem Namen und ändern anschließend im *.vstemplate den Pfad zu der ICO Datei im Knoten <Icon>.

Eine dritte Möglichkeit wäre, Visual Studio eigene Icons zu verwenden. Für unser Template würde beispielsweise das Icon einer “Web Application” gut passen, da es sich bei unserem Template ja auch um eine Webandwendung handelt. Leider hab ich keine Übersicht über bestehende Icons in Visual Studio gefunden — aber einen einfachen Trick.

Wir navigieren wieder zu den Templates, die Visual Studio installiert hat (%VS_InstallDir%/Common7\IDE\ProjectTemplates). Darin sollte sich eigentlich ein Folder Web/1033 befinden. In diesem wiederrum eine Datei names WebApplicationProject.zip. Öffnet man die, findet man die *.vstemplate Datei dieses Templates. In dieser Datei sehen wir, dass der <Icon> Knoten nicht auf eine Datei verweist, sondern auf ein Package und eine ID. Wir kopieren uns diese Knoten und fügen in in unsere *.vstemplate Datei (statt des vorhandenen <Icon> Knotens) ein.

Anschließend müssen wir wieder den VS Template Cache mit devenv.exe /installvstemplates leeren. Das wars. Unser Template hat nun auch ein schönes Icon:

templates_2_2

Das wars. Das Bespieltemplate gibts unter http://downloads.juergenoberngruber.at/blog/CustomTemplate.zip zum Download.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
May 27

HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 1

Heute möcht ich mal etwas zum Thema Erweiterbarkeit von Visual Studio bringen. Unter Visual Studio ist es ja möglich, aus bestehenden Solution-Setups oder bestehenden Dateien wiederverwendbare Vorlagen/Templates zu generieren, um so den Ablauf von wiederkehrenden Aufgaben zu vereinfachen bzw. zu erleichtern. Diese Templates können in Visual Studio importiert werden.

Wie funktioniert das Ganze?
Als erstes erstellen wir uns eine neue Klassenbibliothek, die das MVP Pattern abbilden soll. Dazu brauchen wir unterschiedliche Folder:

  • Models
  • Views
  • Presenter

Außerdem fügen wir in den erstellten Verzeichnissen Beispielklassen, die demonstrieren sollen, wie die einzelnen Komponenten richtig verwendet werden (in einem Real-World-Szenario würden wir hier natürlich Basisklassen haben, die eine korekte Verwendung erzwingen). Schlussendlich sollte unsere Projektstruktur so aussehen:

vstemplates_project_structure

So, nun wollen wir dieses Struktur wiederverwenden, weil es möglicherweise einer Konvention entspricht, die im Unternehmen strickt einzuhalten ist. Visual Studio bietet hierfür eine sehr gute Wizard-Unterstützung. Dieser Wizard ist versteckt unter

->File ->Export Template …

Dort kann man nun zwischen Project Templates und Item Templates wählen. Project Templates erstellen, wie der Name bereits verrät, Vorlagen für ganze Projekte. Item Templates nur für bestimmte Dateien – beispielsweise könnten wir für unseren Presenter eine Basisklasse erstellen, von der jeder Presenter ableiten muss. Diese Vorlage könnten wir nun als Item Template exportieren.

Am Ende des Wizards sollte man nun einen sinnvollen Namen und Beschreibung für das Template vergeben. Außerdem hat man die Möglichkeit festzulegen, ob das neue Template bereits in Visual Studio integriert werden soll und ob das Export-Verzeichnis geöffnet werden soll:

vstemplates_wizard

Was ist passiert?
Visual Studio hat ein *.zip File erstellt, in der alle Elemente aus unserem Projekt inkludiert sind. Außerdem eine Datei mit der Endung *.vstemplate. Das ist eine Xml Datei, die alle notwendigen Metainfos speichert. Zum Beispiel der Name des Templates oder die Dateien, die in dem Template inkludiert sind.

<VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
 <TemplateData>
    <Name>TemplateDemo</Name>
    <Description>My custom Template.</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType></ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>TemplateDemo</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <Icon>__TemplateIcon.ico</Icon>
 </TemplateData>
 <TemplateContent>
    <Project TargetFileName="TemplateDemo.csproj"
             File="TemplateDemo.csproj" ReplaceParameters="true">
       <Folder Name="Models" TargetFolderName="Models">
          <ProjectItem ReplaceParameters="true"
                       TargetFileName="IDemoModel.cs">IDemoModel.cs</ProjectItem>
       </Folder>
       <Folder Name="Presenter" TargetFolderName="Presenter">
          <ProjectItem ReplaceParameters="true"
                       TargetFileName="DemoPresenter.cs">DemoPresenter.cs</ProjectItem>
       </Folder>
       <Folder Name="Properties" TargetFolderName="Properties">
          <ProjectItem ReplaceParameters="true"
                       TargetFileName="AssemblyInfo.cs">AssemblyInfo.cs</ProjectItem>
       </Folder>
       <Folder Name="Views" TargetFolderName="Views">
          <ProjectItem ReplaceParameters="true"
                       TargetFileName="IDemoView.cs">IDemoView.cs</ProjectItem>
       </Folder>
    </Project>
 </TemplateContent>
</VSTemplate>

So, nachdem der Export funktioniert hat und man die Checkbox “Automatically import to Visual Studio” aktiv gelassen hat, ist das Template bereits unter Visual Studio sichtbar:

vstemplates_available

Das wars fürs erste. Im zweiten Teil möchte ich dann noch auf folgende Punkte eingehen:

  • Wie erreiche ich es, dass ich ein Template erstelle, dass mehrere Projekte beinhaltet, die mit unterschiedlichen Suffix im Projektnamen ausgestattet sein sollen?
  • Wie erreiche ich es, dass meine Templates nicht in der Kategorie “My Templates” sondern unter “Visual Studio installed templates” aufscheinen?
  • Wie kann ich meine Templates ohne Visual-Studio-Wizard modifizieren. Beispielsweise, wie kann ich das Icon, dass später neben meinem Template angezeigt wird, austauschen?

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
May 19

Performance des Cassini Webservers in Kombination mit Firefox

Die Performance des Visual Studio eigenen Webservers namens Cassini in Kombination mit Firefox ist ja nicht unbedingt die Beste. Da mich die Tatsache nun schon seit längerm nervt, hab ich mich nun doch endlich dazu entschlossen, der Sache mal auf den Grund zu gehen. Ich war ja schon kurz davor, Microsoft zu unterstellen, da irgendwelche unerlaubten Mittel zu verwenden, um die Performance absichtlich runterzudrücken. Aber …

Kommando retour
Nach kurzer Recherche gibts das Problem nur auf Rechnern, die IPv6 installiert haben. Also mittlerweile sicher schon der Großteil. Um das Problem zu beheben, gibts einen relativ einfachen und sauberen Workaround.

Im FF geht man einfach auf die Seite about:config, auf der man erweiterte Konfigurationseinstellungen findet. Dort gibts auch eine Einstellung namens network.dns.disableIPv6, die man einfach auf den Wert true setzt (Doppelklick).

ff_config

Das wars. Es ist kein Neustart des Browsers notwendig.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Mar 26

HowTo: Deploy ASP.NET MVC auf ASP.NET Hostings

So, heute mal kein Codelastiger Eintrag sondern eher einer in Richtung Konfiguration.

Für einen Freund war ich letztens auf der Suche nach guten Webhostings für ASP.NET MVC. Nachdem ich irgendwie nicht wirklich fündig geworden bin, hab ich mir mal die Frage gestellt: “Warum eigentlich ein spezielles Hosting für ASP.NET MVC?”

Ja warum eigentlich?
ASP.NET MVC ist eigentlich nur ein Aufsatz auf den bereits schon lange bestehenden ASP.NET Kern. Das heißt, es erweitert die bestehende Funktionalität ohne dabei jegliche Konfigurationen an der Hardware bzw. am OS vorzunehmen. Dieser Aufsatz befindet sich in drei kleinen Assemblies:

  • System.Web.Mvc
  • System.Web.Routing
  • System.Web.Abstractions

Deployt man jetzt diese Assemblies mit seiner ASP.NET MVC Anwendung mit auf den Server, muss dort auch nicht zwingend das ASP.NET MVC Framework über einen Installer (sprich im GAC) installiert sein. Wie das Deployment im Detail funktioniert kann man hier nachlesen http://msdn.microsoft.com/en-us/library/dd410407.aspx.

Die Moral von der Geschichte ist jetzt folgende: Um ASP.NET MVC Webseiten zu deployen reicht ein einfachs MS ASP.NET Hosting vollkommen aus. Einzig und alleine die Vorraussetzung, das .NET 3.5 auf dem Server laufen muss schränkt einem beim Suchen nach dem richtigen Webhosting Provider ein.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Mar 09

MVVM: Behandeln von Events über Commands, Teil 2

In meinem letzten Eintrag gings darum, wie man Events eines WPF Controls auf Commands eines ViewModels umleiten kann. Verwendet habe ich dort die Attached Properties von WPF. Das Problem dabei war aber, dass man für diese Attached Properties für ein Control nur einmal verwenden kann. Das heißt, dass man zB für ein Border-Control nicht zugleich den MouseEnter- und MouseLeave-Event auf ein Command umleiten kann. Um das mit der Lösung der Attached Properties zu erreichen, wäre es notwendig, einen zusätzlichen Wrapper rund um das Border-Control einzuführen:


<Border Margin="50"
        commanding:AttachedCommand.Command="{Binding MouseEnterCommand}"
        commanding:AttachedCommand.EventName="MouseEnter">
   <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
           Background="YellowGreen" CornerRadius="20"
           commanding:AttachedCommand.Command="{Binding MouseLeaveCommand}"
           commanding:AttachedCommand.CommandParameter="Border #1"
           commanding:AttachedCommand.EventName="MouseLeave">
   </Border>
</Border>

Um diesem Umweg zu vermeiden, möchte ich heute einen neuen Ansatz präsentieren, der die seit Expression Blend 3 zur Verfügung stehenden Behaviors und Triggers verwendet.

TargetedTriggerAction aus System.Windows.Interactivity verwenden
Die Idee dahinter ist, einen Trigger zu erstellen, der diverse Dependency Properties implementiert. Diese Properties müssen deshalb Dependency Properties sein, weil wir sonst kein Databinding darauf anwenden können. Eine dieser DP ist vom Typ ICommand — also das Command, das wir ausführen wollen, wenn der Trigger gestartet wird:


public ICommand Command
{
   get { return (ICommand)GetValue(CommandProperty); }
   set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
 ´  DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTrigger),
                                new PropertyMetadata(null, OnCommandChanged));

Wichtig bei der Dependency Property ist der Callback OnCommandChanged, der dann aufgerufen wird, wenn der DP ein neuer Wert zugewiesen wird.
Warum ist das wichtig? Bei einer Command-Property, die out-of-the-box bei bereits vorhandenen Controls wie Button vom Framework mitgeliefert wird, wird der Status des Controls abhängig von der CanExecute-Methode des Commands verändert. Also liefert CanExecute false zurück, dann wird der Button, der das Command verwendt, deaktiviert. Genau diese Funktionalität müssen/sollen/wollen wir mit unserem Trigger auch abbilden.

Deshalb registrieren wir uns  in der OnCommandChanged Methode auf den CanExecuteChanged Event, den jede Klasse, die ICommand implementiert, selbstverständlich auch implementieren muss. Sobald dieser Event geworden wurde, wollen wir prüfen, ob unser Control, das ein Binding auf das Command besitzt, noch einen gültigen Status besitzt:


private static void OnCommandChanged(DependencyObject action, DependencyPropertyChangedEventArgs e)
{
   ((CommandTrigger)action)._RefreshCommandHandling((ICommand)e.OldValue, (ICommand)e.NewValue);
}

private void _RefreshCommandHandling(ICommand oldCommand, ICommand newCommand)
{
   if (oldCommand != null) oldCommand.CanExecuteChanged -= new EventHandler((sender, args) => _UpdateCanExecute());
   if (newCommand != null) newCommand.CanExecuteChanged += new EventHandler((sender, args) => _UpdateCanExecute());

   _UpdateCanExecute();
}

Wir bekommen im Eventhandler über die EventArgs den alten und den neuen Wert, den die Dependency Property angenommen hat. Das wir haben die Möglichkeit, uns vom Event des alten Commands (also des alten Wertes der DP) zu entfernen und uns auf den Event des neuen Commands (also der neue Wert der DP) zu registrieren. In der _UpdateCanExecute() Methode prüfen wir schlussendlich, ob das Control, auf dem der Trigger angewendet wird, noch den gültigen Status aufgrund von CanExecute besitzt:


private void _UpdateCanExecute()
{
   if (this.Command == null) return;

   RoutedCommand command = this.Command as RoutedCommand;

   if (command != null) this.IsEnabled = command.CanExecute(this.CommandParameter, this.CommandTarget);
   else this.IsEnabled = this.Command.CanExecute(this.CommandParameter);

   if (this.Target != null && this.SyncOwnerIsEnabled) this.Target.IsEnabled = this.IsEnabled;
}

Im XAML Code wird dieser Trigger jetzt ganz einfach verwendet, indem wir zuerst den richtigen Namespace einbinden und anschließend den Trigger auf ein beliebiges Control anwenden:


<Border>
   <interactivity:Interaction.Triggers>
      <interactivity:EventTrigger EventName="MouseEnter">
         <commanding:CommandTrigger Command="{Binding MouseEnterCommand}" />
      </interactivity:EventTrigger>

      <interactivity:EventTrigger EventName="MouseLeave">
         <commanding:CommandTrigger Command="{Binding MouseLeaveCommand}"  />
      </interactivity:EventTrigger>
   </interactivity:Interaction.Triggers>
</Border>

Das tolle an diesem Trigger ist jetzt, dass er mehrmals auf ein und dem selben Control angewendet werden kann. Wir können jetzt also mehrere Events über den selber Trigger behandeln und auf Commands weiterleiten.

Das Demoprojekt gibts wie immer unter http://downloads.juergenoberngruber.at/blog/CommandAction_Trigger.zip.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Mar 07

MVVM: Behandeln von Events über Commands, Teil 1

Durch WPF ist eine neue Softwarearchitektur sehr in Mode gekommen: MVVM (Model – View – ViewModel; http://en.wikipedia.org/wiki/Model_View_ViewModel). Dank Databinding und Commanding ist diese Softwarearchitektur in WPF relativ einfach umsetzbar. Ein Problem, dass es jedoch nach wie vor gibt, ist die oft fehlende Command-Property bei diversen WPF Controls bzw. die fehlende Möglichkeit, Commands aufgrund von Events aufzurufen.

Ein Beispiel?
Angenommen wir haben eine View, die eine ListView verwendet. Diese ListView stellt einen Event namens “SelectionChanged” zur Verfügung. Wir haben über WPF nun keine Standard-Lösung, wie wir diesen Event auf ein Command einer ViewModel Klasse leiten. Also, wie wir ein Command aufrufen, sobald der Event der ListView gefeuert wird/wurde.

Abhilfe über Attached Properties
Die Lösung liegt in den von WPF eingeführten Attached Properties (http://msdn.microsoft.com/en-us/library/ms749011.aspx).

Wir wollen also erreichen, das ein Command ausgefürt wird, sobald eine Event gefeuert wird. Wir erstellen uns zuerst eine Klasse, die zwei unterschiedliche Attached Properties beinhaltet: Command und EventName. Command ist das Command, das ausgeführt werden soll, sobald der Event gefeuert wird. EventName ist der Name des Events, auf den wir horchen wollen — also der Event, der das Command triggern soll. Wichtig dabei ist, dass wir Callback-Methoden registrieren, die aufgerufen werden, sobald die Attached Property gesetzt wird. In diesen Eventhandler registrieren wir über .NET Reflection eine anonyme Methode als Eventhandler für den Event, der über die Attached Property angegeben wurde:


public class AttachedCommand : DependencyObject
{
   private static IDictionary<DependencyObject, CommandParameter> Parameter { get; set; }

   public static ICommand GetCommand(DependencyObject obj)
   {
      return (ICommand)obj.GetValue(CommandProperty);
   }
   public static void SetCommand(DependencyObject obj, ICommand value)
   {
      obj.SetValue(CommandProperty, value);
   }
   public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(AttachedCommand), new UIPropertyMetadata(CommandChanged));

   public static string GetEventName(DependencyObject obj)
   {
      return (string)obj.GetValue(EventNameProperty);
   }
   public static void SetEventName(DependencyObject obj, string value)
   {
      obj.SetValue(EventNameProperty, value);
   }
   public static readonly DependencyProperty EventNameProperty =
       DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(AttachedCommand), new UIPropertyMetadata(EventNameChanged));

   public static void CommandChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
   {
      if (!Parameter.ContainsKey(dependencyObject)) Parameter.Add(dependencyObject, CommandParameter.Default);

      Parameter[dependencyObject].Command = args.NewValue as ICommand;

      _AttachEventHandler(dependencyObject);
   }

   public static void EventNameChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
   {
      if (!Parameter.ContainsKey(dependencyObject)) Parameter.Add(dependencyObject, CommandParameter.Default);

      Parameter[dependencyObject].EventName = args.NewValue as string;

      _AttachEventHandler(dependencyObject);
   }

   private static void _AttachEventHandler(DependencyObject dependencyObject)
   {
      if (dependencyObject == null) throw new ArgumentNullException("DependencyObject is null");
      if (!Parameter.ContainsKey(dependencyObject)) return;

      CommandParameter parameter = Parameter[dependencyObject];
      if (parameter.Command == null || string.IsNullOrEmpty(parameter.EventName)) return;

      EventInfo eventInfo = dependencyObject.GetType().GetEvent(parameter.EventName);
      if (eventInfo == null)
          throw new InvalidProgramException(string.Format("Cannot find am event with the name <{0}>.", parameter.EventName));

      parameter.Callback = _CreateHandler(eventInfo, () => { parameter.Command.Execute(null); });

      eventInfo.AddEventHandler(dependencyObject, parameter.Callback);
   }
}

Parameter speichert eine Liste von CommandParameter, die notwendige Metadaten für das Commanding speichert. Wir brauchen diese Liste deshalb, weil wir die Attached Properties ja auf unterschiedlichen WPF Controls verwenden können. In der _AttachEventHandler Methode holen wir uns den Event über Reflection und speichern einen neuen Eventhandler für den Event, der schlussendlich das Command ausführt.

In der View können wir diese Attached Properties jetzt ganz einfach verwenden, in dem wir zuerst den Namespace einbinden und anschließend auf einem beliebigen Controls die Properties anwenden:

<Window x:Class="CommandAction.Views.Master"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:commanding="clr-namespace:CommandAction.Commanding"
             xmlns:viewmodels="clr-namespace:CommandAction.ViewModels"
             Title="Master" Height="306" Width="722"
             WindowStyle="ToolWindow" WindowStartupLocation="CenterScreen">

    <Window.DataContext>
        <viewmodels:MasterViewModel />
    </Window.DataContext>

    <Border Margin="50" CornerRadius="20" Background="YellowGreen"
               commanding:AttachedCommand.Command="{Binding MouseLeaveCommand}" commanding:AttachedCommand.EventName="MouseLeave">
    </Border>

</Window>

Problem?
Soweit so gut. Das einzige Problem das wir über diese Methode bekommen ist, dass wir für ein WPF Control nur einen Event auf ein Command umleiten können, weil für die Attached Properties nur einmal auf ein WPF Control anwenden können. Wollen wir mehrere Events auf Commands umleiten, müssen wir uns einen anderen Weg einfallen lassen. Angenommen wir wollen für ein Image Control den MouseEnter und MouseLeave Event über zwei unterschiedliche Commands behandeln, können wir das mit der Attached Property Variante nicht umsetzen (außer wir wrappen rund um das Image Control ein anderes Control).
Lösen können wir das mit den seit Expression Blend 3 zur Verfügung stehenden Behaviors/Triggers. Wie das ganze funktioniert, erklär ich in meinem nächsten Post, der in den nächsten Tagen kommen wird.

Das Demoprojekt gibts wie immer unter http://downloads.juergenoberngruber.at/blog/CommandAction.zip zu finden.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Feb 27

INotifyPropertyChanged mit Hilfe von AOP lösen

Heute möchte ich einmal eine Technik vorstellen, die ich schon öfters bei meinen WPF Projekten eingesetzt habte. Verwendet man in WPF die MVVM-Architektur (ModelView – View – Model), so kommt man um das Konzept des mächtigen WPF Databindings nicht herum. Wer mit Databinding noch nicht so vertraut ist, soll sich zuerst mal hier http://msdn.microsoft.com/en-us/library/ms750612.aspx etwas schlau machen.

Das interessante am WPF Databinding ist die Möglichkeit, Werte bidirektional zu binden: Das heißt, dass man einerseits die Daten von der Datenquelle zur Datensenke binden kann und andererseits auch Änderungen der Datensenke automatisch in die Datenquelle übernehmen kann. In MVVM würde das bedeuten, dass man auf der View (XAML) UI-Controls aus Daten des ViewModels befüllen kann und Änderungen, die auf der View auftreten automatisch zurück in das ViewModel speichern kann, ohne dabei eine Benutzerinteraktion zu benötigen. Wichtig ist auch, dass bereits gebundene Werte auf der View automatisch aktualisiert werden sollen, sobald im ViewModel – also bei der Datenquelle – Änderungen auftreten.

INotifyPropertyChanged oder DependencyProperties
Um ein automatisches Binding-Update zu erreichen, gibt es zwei unterschiedliche Wege, das zu erreichen:

  1. Man leitet eine Klasse von DependencyObject ab und implementiert DependencyProperties. Da ein ViewModel normalerweise nicht von DependencyObject ableitet, ist diese Möglichkeit im Kontext von MVVM nicht wirklich brauchbar. Mehr über DependencyObject und DependencyProperty gibts unter http://msdn.microsoft.com/en-us/library/system.windows.dependencyobject.aspx
  2. Man implementiert das Interface INotifyPropertyChanged (http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx). Dieses Interface implementiert einen Event names PropertyChanged, der gefeuert wird, sobald sich eine Property bzw. der Wert einer Property verändert hat. Bindet man nun in der View (XAML) auf eine Property eines ViewModels, muss diese Property jedesmal, wenn sich der Wert der Property verändert, diesen Event feuern, um automatisch das Databinding zu aktualisieren. Wirft es den Event nicht, gibts auch keine automatische Aktualisierung der View.

Das war mal das Grundgerüst, dass wir für unsere weiteres Thema heute benötigen.

Wo ist das Problem?
Sehen wir uns einmal folgende Klasse an, die INotifyPropertyChanged implementiert und ein paar Properties für das Databinding zur Verfügung stellt:


public class ApplicationViewModel : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

    private string _ApplicationTitleField = string.Empty;
    public string ApplicationTitle
    {
       get { return _ApplicationTitleField; }
       set
       {
          _ApplicationTitleField = value;
          if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ApplicationTitle"));
       }
    }

    private string _ApplicationVersionField = string.Empty;
    public string ApplicationVersion
    {
        get { return _ApplicationVersionField; }
        set
        {
           _ApplicationVersionField = value;
           if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ApplicationVersion"));
        }
     }
}

So, wo ist hier jetzt das Problem? Genau, die Properties selbst. Im Grunde genommen ist es kein Problem bzw. ist syntaktisch alles in Ordnung. Ich persönlich habe aber ein Problem damit, aus den Automatic Properties “alte .NET 2.0″ Properties zu erzeugen, nur um im Setter der Property den PropertyChanged Event feuern zu können. Wie lösen wir das Ganze jetzt: Wir verwenden ein tolle Bibliothek names PostSharp, die die Prinzipen von AOP (Aspect Oriented Programming) implementiert.

Lösung: AOP mittel PostSharp
Mit AOP können wir Funktionalität in sogenannte Aspekte auslagern und diese nach Bedarf wiederverwenden. Genau so einen Aspekt können wir jetzt für unser Problem mit dem PropertyChanged Event einsetzen. Wir können hergehen und uns selbst ein die notwendige Funktionalität programmieren oder wir verwenden bereits vorhandene Bibliotheken, die uns bei unserer Problemlösung unterstützen.

In der Community gibt es mittlerweile eine handvoll verfügbaren Bibliotheken, die AOP Funktionalität für .NET zur Verfügung stellen. Mein persönlicher Favorit ist auf alle Fälle PostSharp entwickelt von Gael Fraiteur. Einer der großen Vorteile von PostSharp ist , dass sich die Bibliothek in den MSBuild Process hängt, direkt den CIL Code manipuliert und somit keine Perfomanceeinbussen entstehen. Wie gehen wir jetzt also vor, um unser Problem der alten .NET Properties mit Hilfe eines Aspekts zu lösen?

  1. Zuerst laden wir uns mal PostSharp herunten und installieren es: http://www.sharpcrafters.com/postsharp/download
  2. Um PostHsarp verwenden zu können, müssen wir 2 Referenzen zu unserem Projekt hinzufügen: PostSharp.Public und PostSharp.Laos
  3. Dann erstellen wir uns den neuen Aspekt. Im Grunde ist das eine einfach Klasse, die von einer bestimmten Basisklasse ableitet und als Serializeable gekenntzeichnet werden muss:
    [Serializable]
    public class NotifyAspect : OnMethodBoundaryAspect
    {
    }
    
  4. Die Basisklasse OnMethodBoundaryAspect stellt PostSharp zur Verfügung. Diese Klasse bietet eine Methode names OnExit an, die wir in unserem Apsekt einfach überschreiben und implementieren. Diese Methode wird aufgerufen, wenn eine Methode verlassen wird — also wenn das Ende einer Methode erreicht wurde. Da wir unseren Aspekt für Properties verwenden, ist unsere Methode eigentlich der Getter bzw. Setter – Zugriff der Property:
    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
       if (!(eventArgs.Instance is ViewModelBase))
          throw new InvalidCastException("Cannot raise PropertyChanged on viewmodel that doesn't derived from ViewModelBase");
    
       if (!eventArgs.Method.IsSetter()) return;
    
       ViewModelBase viewModel = eventArgs.Instance as ViewModelBase;
    
       viewModel.OnPropertyChanged(eventArgs.Method.SetterName());
    }
    

    Wir prüfen,ob die Instanz, zu der die Methode gehört von einer bestimmten Basisklasse ist. Ist das nicht so, dann werfen wir einen Fehler, da der Aspekt nur mit bestimmen Typen umgehen kann. Warum? Weil wir später eine Methode der Instanz aufrufen müssen. Jetzt überprüfen wir, ob die Methode, für die der Aspekt aufgerufen wird eigentlich eine Setter-Methode einer Property ist. Das haben wir in eine Extension-Methode ausgelagert (der detaillierte Code ist im Beispielprojekte nachzulesen). Jetzt können wir casten und eine bestimmte Methode der Basisklasse aufrufen: OnPropertyChanged wirft im Endeffekt den PropertyChanged Event des Interfaces INotifyPropertyChanged. Diesen Event können wir nur in der Klasse werfen, in der der Event auch definiert wurde. Daher müssen wir hier in unserem Aspekt diesen Umweg gehen.

    public void OnPropertyChanged(string propertyName)
    {
       if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Propertyname is null or too short.");
    
       PropertyInfo info = this.GetType().GetProperty(propertyName);
       if (info == null) throw new InvalidProgramException("The requested property doesn't exist.");
    
       if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    
  5. Jetzt fehlt uns nur noch, dass wir den Aspekt in unserem ViewModel verwenden:
    public class ApplicationViewModel : ViewModelBase
    {
       [Notify]
       public string ApplicationTitle { get; set; }
    
       [Notify]
       public string ApplicationVersion { get; set; }
    }
    

Wir können uns jetzt eine View bzw. XAML Seite bauen, die dieses ViewModel als DataContext gesetzt hat und auf die beiden Properties databinden. Das Demoprojekt ist wie immer unter http://downloads.juergenoberngruber.at/blog/INotifyPropertyChangedWithAOP.zip zu finden.

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Feb 24

HowTo: Custom Cursor für Windows Forms

Heute wieder ein kurzer Eintrag: Letztens wollte ich in einer WinForms Anwendung bei gewissen Aktionen Cursor verändern. .NET liefert zwar ein paar Curors-Typen (System.Windows.Forms.Cursors) mit, die befriedigen bei sehr individuell angepassten und oft auch selbstgerenderten WinForms Anwendungen nicht ganz. Daher will ich heute kurz beschreiben, wie man seinen eigenen Cursor aus einem lokalen File bzw. Resource lädt und verwendet.

Was benötigen wir?
Das einzige, was wir brauchen ist eine einfache *.ico Datei, die wir später als Cursor darstellen wollen. Am besten erstellt man sich so ein File mit einem Bildverarbeitungsprogramm seines Vertrauens. Hat man dass, können wir bereits mit Visual Studio weitermachen. Wir erstellen uns ein WinForms Projekt und fügen das zuvor erstellte *.ico File zum Projekt hinzu und hängen es in die Resources des Projekts.

Jetzt können wir über den Properties-Namespace des Projekts auf die Resources zugreifen und sollten dort über Intellisense unsere soeben hinzugefügte Grafik finden. Die ist vom Type Icon — die wiederrum eine Property names “Handle” verfügt. Genau diesen IntPtr übergeben wir dem Konstruktor der Cursor-Klasse und weisen diese Cursor-Instanz auf den aktuellen Cursor der Anwendung zu. Fertig.

Cursor.Current = new Cursor(Properties.Resources.Cursor_Wait.Handle);

Aussehen tut das Ganze dann so:

Custom Cursor

Demoprojekt gibts wie immer hier zu finden: http://downloads.juergenoberngruber.at/blog/CustomCursor.zip

In diesem Sinne.

Read More 0 Comments   |   Posted by incubi
Previous Page 1 of 3
  • Recent Posts
    • HowTo: Eigene Regeln für Microsoft StyleCop erstellen
    • HowTo: Microsoft StyleCop Integration mit Visual Studio und MSBuild
    • HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 2
    • HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 1
    • Performance des Cassini Webservers in Kombination mit Firefox
  • Archives
    • June 2010 (2)
    • May 2010 (3)
    • March 2010 (3)
    • February 2010 (4)
    • January 2010 (3)
    • December 2009 (1)
    • November 2009 (9)
  • Tags
    .net AOP ASP.NET ASP.NET MVC blend Bootmanager C# ci Codequality Configuration Continuous Integration css Cursor DateTime DDD Deployment dynamisch emit Exrpession Extensibility Fluent HowTo Microsoft MVVM Pattern PostSharp Reflection Repository ruby silverlight Software Design StyleCop System teamcity Templates VHD Virtual Images Visual Studio Vorlagen web Windows 7 Windows Mobile WinForms WPF XAML
  • About

    Jürgen Oberngruber is a project manager and software architect living in Wels, Austria and currently working at ecomplexx Austria, Wels. During his study at the University of Applied Sciences in Hagenberg, Austria he gained a deep knowledge in the field of software engineering using a lot of different programming languages. Since a few years he's focusing on the Microsofts .NET platform including all relevant technologies. One of his passion is to explore, to test and to evaluate new technologies and programming languages (mostly in the field of Microsofts .NET platform). Checkout more information on www.juergenoberngruber.at

  • Meta
    • Log in
    • Entries RSS
    • Comments RSS
    • WordPress.org
  • Archives
    • June 2010
    • May 2010
    • March 2010
    • February 2010
    • January 2010
    • December 2009
    • November 2009
  • Search






  • Home

© Copyright Jürgen Oberngruber's Blog. All rights reserved.
Designed by FTL WordPress Themes brought to you by DT Web Template

Back to Top