Archiv für den Monat November 2015

Code mit T4 Templates in Visual Studio generieren


Die meisten haben T4 Templates (Text Template Transformation Toolkit) bestimmt schon verwendet ohne zu wissen das es sich dabei um die Dateien mit der Endung “*.tt” handelt. Wenn man z.B. das Entity Framework verwendet wird im Hintergrund zur Entity Generierung ein T4 Template verwendet und auch bei ASP.NET MVC werden für das Erstellen von Views und Controllern T4 Templates verwendet, die man auch bei Bedarf an seine eigenen Bedürfnisse anpassen kann.

1. Erstellen eines T4 Templates

Wenn man selbst ein T4 Template anlegen möchte geht dies problemlos mit Visual Studio unter “Neues Item” anlegen und das “Text Template” auswählen und einen passenden Namen vergeben. Standardmäßig erhält die Datei die mit dem T4 Template dann später erstellt wird, den gleichen Namen wie das T4 Template und erhält nur eine andere Endung, z.B. cs oder txt.

image

Es gibt zwei Möglichkeiten wie man das Erstellen der Codeausgabe anstoßen kann. Einmal kann man mit der rechten Maustaste auf das T4 Template im Solution Explorer klicken und “Run Custom Tool” auswählen und schon wird der Code (die Ausgabe) neu erstellt/generiert. (Bei einem leeren Template natürlich auch nur eine leere Datei, diese Datei befindet sich Standardmäßig “unterhalb” des T4 Templates, welches man “aufklappen” kann.)

image

Eine weitere Möglichkeit zum Erstellen der Ausgabe ist das einfache Abspeichern des T4 Templates, wenn dies im Visual Studio geöffnet ist, hier wird bei jedem Speichern versucht den Ausgabecode zu erstellen.

2. Visual Studio Support Syntax highlighting und IntelliSense

Leider bietet die Grundinstallation von Visual Studio kein Syntax highlighting oder IntelliSense für T4 Templates. Es sollte nach einer passenden Extension gesucht werden, wenn man selbst ein T4 Template erstellen möchte. Wenn man Besitzer einer Lizenz von Resharper ist, hat man glück, denn hier gibt es eine sehr gute Extension mit dem Namen “ForTea” und diese bietet Syntax highlighting und IntelliSense. Wenn man keine Resharper Lizenz hat, kann man auf die kostenlose Version von “DevArt T4Editor” zurückgreifen oder von “tangible T4 Editor”.

3. T4 Syntax/Markup und Aufbau

Wenn man eine neue T4 Datei erstellt sieht diese im Normalfall folgendermaßen aus

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>

Die Erste Zeile “template” Enthält dabei allgemeine Informationen für das Template selbst. Wenn man “hostpsecific” auf True stellt, dann erhält man im Template zugriff auf eine weitere lokale Variable mit dem Namen “Host”, mit der man z.B. den Aktuellen Pfad des T4 Templates auslesen kann “this.Host.TemplateFile”.

Die Sprache in “language” ist die Sprache mit der im Template programmiert wird und es handelt sich NICHT um die Sprache der Ausgabedatei.

Mit “debug” auf True kann man im Template Regions für “#if DEBUG” anlegen außerdem werden die Sourcen und debugging Symbols für das T4 Template selbst abgelegt unter “C:\Users\%AccountName$\AppData\Local\Temp\”.

Durch “assembly” lassen sich Externe Assemblies einbinden die man für die Codeerstellung benötigt, diese müssen im GAC vorhanden sein damit diese hier direkt angegeben werden können ohne Pfad. Wenn man eigene DLLs einbinden möchte, die sich nicht im GAC befinden, muss der komplette Pfad zur DLL angegeben werden (eine relative Angabe ist nicht möglich!). Dabei kann auf die Variablen “TargetDir” oder “SolutionDir” zugegriffen werden z.B.

<#@ assembly name="$(TargetDir)ProxyGenerator.dll" #>
<#@ assembly name="$(SolutionDir)MySolutionName\bin\x86\Release\ProxyGenerator.dll" #>

Zum “namespace”  muss glaube ich nichts gesagt werden, dieser funktioniert wie in allen anderen .NET Klassen.

Wie bereits weiter oben beschrieben erstellt ein T4 Template normalerweise eine Datei.

image

Über “output” und der zugehörigen “extension” kann angegeben werden um welche Dateiendung es sich bei der generierten Datei handeln soll. Da es mit zusätzlichem Code auch möglich ist mehrere Ausgabedateien zu erstellen, möchte man evtl. nicht das eine Datei mir dem Namen des T4 Templates erstellt wird und hier kann man z.B.

<#@ output extension="//" #>

verwenden als “Hack” denn es wird in der Ausgabe zumindest nur eine Warnung angezeigt und kein Fehler. Es wird aber keine Standard Ausgabedatei mehr generiert.

image

4. Code Blöcke <#, <#= und <#+

Bisher handelte es sich immer um Blöcke die in das “<#@ … #>” eingefasst werden. Es gibt aber noch andere Blöcke.

Mit dem “<#= GetText() #>” wird einfach der Rückgabewert der Funktion oder der Wert einer Variable direkt in die Ausgabedatei geschrieben. Mit dem Block “<# var name=”Squad”;#> können Codeschnipsel ausgeführt werden, wie Variablen Deklarationen oder ForEach schleifen. Aus dem einfachen Template

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>

<# var name = "Squad"; #>
Hallo mein Name ist <#=name#>Wuschel

Wird dann folgende Ausgabe erstellt

Hallo mein Name ist SquadWuschel

Die Deklaration von Funktionen oder Klassen ist nur im Block “<#+ string GetText() … #>” möglich.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>

<# var name = "Squad"; #>
Hallo mein Name ist <#=name#>Wuschel
<#= GetText() #>

<#+
    string GetText() { return "Neuer Text"; } 
#>

Dabei wird die folgende Ausgabedatei generiert

Hallo mein Name ist SquadWuschel
Neuer Text

Ein weiteres Beispiel unter Verwendung einer for Schleife

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>

<# var name = "Squad";
   for (int i = 0; i < 5; i++)
   {
     this.Write("Text mit Write Erstellt Nr: " + i + "\r\n");    
#>
Hallo mein Name ist <#=name #>Wuschel und ich bin Nummer: <#=i#>
<#=GetText(i) #>
<# } #>

<#+ string GetText(int nummer)  {  return "Neuer Text Nummer: " + nummer;   }  #>

Damit wird die folgende Ausgabe erstellt

Text mit Write Erstellt Nr: 0
Hallo mein Name ist SquadWuschel und ich bin Nummer: 0
Neuer Text Nummer: 0
Text mit Write Erstellt Nr: 1
Hallo mein Name ist SquadWuschel und ich bin Nummer: 1
Neuer Text Nummer: 1
Text mit Write Erstellt Nr: 2
Hallo mein Name ist SquadWuschel und ich bin Nummer: 2
Neuer Text Nummer: 2
Text mit Write Erstellt Nr: 3
Hallo mein Name ist SquadWuschel und ich bin Nummer: 3
Neuer Text Nummer: 3
Text mit Write Erstellt Nr: 4
Hallo mein Name ist SquadWuschel und ich bin Nummer: 4
Neuer Text Nummer: 4

5. Debuggen von T4 Templates

Seit Visual Studio 2012 ist es auch möglich ein T4 Template direkt zu debuggen im Visual Studio. Wenn man mit der rechten Maustaste auf das T4 Template klickt gibt es einen Punkt “Debug T4 Template”, vorher sollte man aber noch den passenden Haltepunkt setzen, damit der Debugger auch weiß wo er halten soll.

image

Leider kann man nicht direkt über die Variablen im Template gehen um zu sehen welcher Wert gerade in der Variablen X steht. Wenn man aber das “Locals” Fenster öffnet stehen automatisch alle aktuellen Variablen zur Verfügung und man sieht die passenden Werte.

image

image

Hinweis: Wenn das Template etwas größer wird und man eine eigene DLL einbindet, die man parallel in der gleichen Solution entwickelt, dann gibt es beim Debuggen ein Problem. Denn wenn man ein T4 Template debuggt, wird ein eigener Prozess erstellt “T4VSHostProcess.exe”.

image

Und dieser Prozess wird nicht wieder geschlossen, wenn das Debuggen des T4 Templates vorbei ist. Wenn man aber in seinem T4 Template auf eine DLL aus der aktuellen Solution verweist, die auch jedes mal mit gebaut werden soll, dann kommt es zu einem Fehler das die DLL bereits verwendet wird.

<#@ assembly name="$(TargetDir)ProxyGenerator.dll" #>

Das kann mit dem folgenden Eintrag im T4 Template Eintrag umgangen werden

<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>

Damit wird der T4VSHostProcess nach jedem Debuggen wieder geschlossen.

Quellen:

http://www.olegsych.com/2008/02/t4-template-directive/

http://www.olegsych.com/2008/02/t4-class-feature-blocks/

Wer noch weitere Tipps zum Arbeiten mit T4 Templates hat, freue ich mich immer gern über einen Kommentar.

Advertisements

CopyToClipboard Directive mit AngularJs, TypeScript und ControllerAs Syntax


Bisher war Flash notwendig um auf einer Webseite einen Button anzubieten, der einen Text in die Zwischenablage kopiert. Wenn man aber das Glück hat und bei seiner Anwendung nur die neuesten Browser unterstützen muss, kann man jetzt auch auf eine native JavaScript Funktion zurückgreifen.

$("#inputText").select();
document.execCommand("copy");

Denn man kann mit diesen zwei Zeilen Code ab Chrome:42+, FF:41+, IE9+ und Opera29+ (Safari:nicht unterstützt) den Text z.B. in einer Textbox auswählen, den man dann in die Zwischenablage kopieren möchte.

Meine Direktive kann folgendermaßen verwendet werden in einem “div” oder jedem beliebigen anderen Element:

<div sq-copy-to-clipboard ng-model="ctrl.viewModel.Name" sq-call-back-fct="ctrl.DoSomething" sq-icon-css="'fa fa-fw fa-copy'" sq-btn-css="'btn btn-default btn-xs'" sq-title="'In die Zwischenablage'"></div>

In der AngularJS Direktive “sqCopyToClipboard” möchte ich aber nur einen Button anbieten der ein ngModel Element enthält und dessen Wert in der Zwischenablage ablegt. Damit dies funktioniert, wird mit Hilfe von jQuery jedes mal wenn der Button zum Kopieren des ngModel Wertes geklickt wird, ein Temporäres HTML Textfeld erstellt was nur 1x1px groß ist und am Ende des DOMs angehängt wird. Dann wird der Text darin markiert und kopiert und danach wird das Element auch gleich wieder aus dem DOM entfernt. Das Element muss 1x1px groß sein, damit Chrome den Text darin auch markieren kann. Denn bei 0x0px größe kann Chrome keinen Text markieren.

In der Direktive selbst verwende ich die “controllerAs” Syntax, die mit AngularJs 1.2 eingeführt wurde und die neueste Version von “bindToController”, die seit AngularJs 1.4 existiert. Der $scope wird damit nicht mehr benötigt und muss auch nicht mehr in der Controllerfunktion injected werden. Denn auch in Direktiven kann jetzt genau wie beim Controller die “ControllerAs” Syntax verwendet werden. Damit kann man im Template der Direktive über den “ControllerAs” Namen z.B. “sqCopyPasteCtrl” auf die Variablen zugreifen die man in “bindToController” definiert hat und auch auf alle weiteren Variablen und Funktionen die man in der Controllerfunktion über “this” zur Verfügung stellt. Zur genauen Funktionsweise von “controllerAs” kann ich nur den Blogeintrag “Exploring Angular 1.3 Binding to Directive Cotnrollers” empfehlen.

HTML Template meiner Directive Link “a” mit Icon Class:

<a ng-click="sqCopyPasteCtrl.copyToClipboard()" ng-class="sqCopyPasteCtrl.sqBtnCss" title="{{sqCopyPasteCtrl.sqTitle}}">
<i ng-class="sqCopyPasteCtrl.sqIconCss"></i></a>

Die Direktive selbst in TypeScript verfasst.

module App.Directives {
    /* 
    * Definition der Scope Variablen
     */
    export interface ICopyToClipboardScope {
        sqValues : string;
        sqTitle : string;
        sqIconCss : string;
        sqBtnCss: string;
        sqCallBackFct;
    }

    /*
     * Directive, die den String im ngModel in die Zwischenablage kopiert. 
     * https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
     * ACHTUNG geht nur ab Chrome:42+, FF:41+, IE:9+, Opera:29+, Safari:Not Supported
     *
     * Verwendung: 
     *</pre>
<div></div>
<pre>
     */
    export class CopyToClipboard implements ng.IDirective {
        public restrict: string = "A";
        public replace: boolean = true;
        public require = "ngModel";
        public templateUrl: string = 'ScriptsApp/directives/templates/copyToClipboard.directives.html';
        public scope = {}

        public controller = CopyToClipboardCtrl;
        public controllerAs = "sqCopyPasteCtrl";
        public bindToController: ICopyToClipboardScope = {
            sqValues: "=ngModel",    //Der Wert der in die Zwischenablage kopiert werden soll.
            sqTitle: "=",            //Der Titel der angezeigt werden soll Optional
            sqIconCss: "=",          //CSS für das Icon
            sqBtnCss: "=",           //CSS für den Button
            sqCallBackFct: "&"        //Callback Funktion die ausgeführt wird, wenn diese gesetzt wurde.
        }

        constructor() { }

        //#region Angular Module Definition
        private static _module: ng.IModule;
        /**
        * Stellt die Angular Module für CopyToClipboard bereit.
        */
        public static get module(): ng.IModule {
            if (this._module) {
                return this._module;
            }

            //Hier die abhängigen Module für unsere Direktive definieren.
            this._module = angular.module('copyToClipboard.directives', [CopyToClipBoardServiceProvider.module.name]);
            this._module.directive('sqCopyToClipboard', [() => { return new CopyToClipboard(); }]);
            return this._module;
        }
        //#endregion
    }

    /*
    * Implementierung unseres CopyToClipboard Controllers.
    */
    export class CopyToClipboardCtrl implements ICopyToClipboardScope {
        public sqValues: any;
        public sqTitle: string;
        public sqIconCss: string;
        public sqBtnCss: string;
        public sqCallBackFct;

        static $inject = ['copyToClipBoardConfig'];

        /* 
        * Da wir die CSS Klassen für einen Provider setzen, hier den passenden Provider injecten und im Template dann auf dessen Config Werte zugreifen.
        */
        constructor(private copyToClipBoardConfig: ICopyToClipBoardServiceProvider) {
            this.init();
        }

        init(): void {
            //Prüfen ob ein Titel oder andere CSS Klasse übergeben wurde, sonst den Wert aus dem Provider verwenden
            if (this.sqTitle === undefined) {
                this.sqTitle = this.copyToClipBoardConfig.config.title;
            }

            if (this.sqBtnCss === undefined) {
                this.sqBtnCss = this.copyToClipBoardConfig.config.btnCssClass;
            }

            if (this.sqIconCss === undefined) {
                this.sqIconCss = this.copyToClipBoardConfig.config.iconCssClass;
            }
        }

        /*
        * Unseren ModelValue in die Zwischenablage kopieren.
        */
        public copyToClipboard(): void {
            var inputId: string = "sqCopyToClipboardText";
            
            //Unser Input erstellen inkl. des Textes den wir kopieren wollen, da die Angular Implementierung auf "this.sqValues" zugreift ist dies 
            //durch die Extra Definition des CopyToClipboard Controllers problemlos möglich. Das Input selbst ist Mindestens 1px breit und hoch, denn
            //sonst kann der Inhalt im Chrome nicht markiert werden, was zwingend notwendig ist damit der Inhalt kopiert werden kann.
            var input = $(``);

            try {
                //Unser Input dem DOM Hinzufügen
                $(input).appendTo(document.body);
                //Den Inhalt des Inputs auswählen, denn der execCommand kopiert nur die Inhalte in die Zwischenablage, die ausgewählt sind.
                $(`#${inputId}`, document.body).select();
                document.execCommand("copy");
            } finally {
                //Am Ende das Eingabefeld wieder aus dem DOM entfernen
                $(`#${inputId}`, document.body).remove();

                //Sollte eine CallBack Funktion angegeben sein, diese hier ausführen.
                if (this.sqCallBackFct !== undefined) {
                    this.sqCallBackFct();
                }
            }
        }
    }

    /*
     * Configklasse für unsere CopyPaste Direktive. Hier kann man z.B. die passenden Css Klassen ändern die gesetzt werden.
     */
    export class CopyToClipBoardServiceProvider implements ng.IServiceProvider, ICopyToClipBoardServiceProvider {
        //Die Konfigurationsdaten entsprechend setzen.
        config: ICopyToClipBoardConfig = {
            btnCssClass: "btn btn-default btn-sm",  //Bootstrap
            iconCssClass: "fa fa-fw fa-copy",       //Font Awesome
            title: "In die Zwischenablage kopieren"
        }

        $get = () => {
            return {
                config: this.config
            }
        };


        //#region Angular Module Definition
        private static _module: ng.IModule;
        /**
        * Stellt die Angular Module für CopyToClipboardProvider bereit.
        */
        public static get module(): ng.IModule {
            if (this._module) {
                return this._module;
            }

            //Hier die abhängigen Module für unsere Direktive definieren.
            this._module = angular.module('copyToClipBoardConfig.provider', []);
            this.module.provider("copyToClipBoardConfig", () => new CopyToClipBoardServiceProvider());
            return this._module;
        }
        //#endregion

    }

    export interface ICopyToClipBoardServiceProvider {
        config: ICopyToClipBoardConfig;
    }

    export interface ICopyToClipBoardConfig {
        btnCssClass: string;
        iconCssClass: string;
        title: string;
    }
}

Den Aktuellen Quellcode findet Ihr auch auf meinem GitHub Account.