Archiv der Kategorie: T4

Laden von 64Bit DLLs in 32Bit Anwendung mit Reflection


Da könnte man sich fragen wozu man sowas braucht. Aber es gibt immer einen guten Grund und in meinem Fall ging es darum, das ich in einem T4 Template 64Bit DLLs laden musste und diese nach Benutzerdefinierten Attributen zu durchsuchen um dann eine passende Ausgabe zu erstellen. Leider gibt es die Konsolenanwendung mit der VisualStudio die T4 Templates ausführt nur als 32 Bit Anwendung, daher musste eine andere Lösung her und die hieß, laden von 64Bit DLLs in einer 32 Bit Anwendung.

Wenn man also 64Bit DLLs in einer 32Bit Anwendung laden möchte, kann man nicht mehr einfach Assembly.LoadFrom(“FilePathDll”) verwenden, was einem die passende DLL inklusive aller Abhängigkeiten lädt, sondern man muss Assembly.ReflectionOnlyLoadFrom(“FilePathDll”) verwenden. Diese Funktion lädt leider nicht mehr automatisch alle Abhängigkeiten, sondern diese muss man “manuell” nachladen. Dafür stellt .NET einen Eventhandler bereit AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve, an dem man einfach eine passende Funktion registrieren muss, die immer dann aufgerufen wird, wenn eine abhängige Assembly benötigt wird (aufgelöst werden muss) und die Funktion gibt die passende Assembly zurück.

Das Laden von Assemblies und der passende Eventhandler könnten dann z.B. im einfachsten Fall folgendermaßen implementiert werden

 

Ein weiterer “Vorteil” dieser Lösung (zumindest in meinem Fall),  das für alle Dateien die im übergebenen Verzeichnis aufgelöst werden können (NUR NACH NAME keine Versionsnummer), die Assembly Redirects ignoriert werden. Das gilt für alle DLLs die wir im Snippet mit Assembly.ReflectionOnlyLoadFrom(pathWithDll) aufrufen, da wir hier vorher kein ApplyPolicy anwenden. Das Problem bei T4 Templates ist nämlich, das es im Ausführungskontext von Visual Studio ausgeführt wird und damit nicht die Assembly Redirects z.B. aus der web.config oder der app.config im Projekt verwendet, sondern nur die Redirects die in der app.config des Visual Studios angegeben sind.

Ein weiterer und leider auch entscheidender Nachteil an dieser Lösung ist das man eigene Attribute nicht mehr so einfach suchen kann. Denn die Funktion GetCustomAttributes(…) kann nicht verwendet werden, wenn man Assembly.ReflectionOnlyLoadFrom verwendet. Hier steht einem nur die Methode GetCustomAttributesData() zur Verfügung, mit der das Filtern nach eigenen Attributen nicht mehr ganz so komfortabel ist.

Hinweis: Probleme hatte ich außerdem, beim Vergleichen von Typen

assembly.GetCustomAttributesData().Where(p => p.AttributeType == typeof(MeinAttribut))

hier haben die Hashcodes nicht übereingestimmt, obwohl es sich um das gleiche Attribut/Typen handelte, daher habe ich dann nach FullName verglichen und das hat dann funktioniert, hier weiß ich leider nicht woran das liegt.

assembly.GetCustomAttributesData().Where(p => p.AttributeType.FullName == typeof(MeinAttribut).FullName)

Quelle: http://blog.slaks.net/2013-12-25/redirecting-assembly-loads-at-runtime/

Advertisements

T4 Template als Service/Proxybuilder für AngularJs TypeScript Services


Wenn man einen AngularJs HTTP Service erstellt der die Verbindung zum .NET Controller herstellen soll um Daten abzufragen oder zu speichern, handelt es sich immer um den “gleichen” boilerplate Code der geschrieben werden muss. Genau hier setzt mein T4 Template der Proxybuilder an und generiert die passenden AngularJs Services in JavaScript oder auch in TypeScript und in Verbindung mit TypeLite werden komplett typisierte Services erstellt.

Damit das ganze funktioniert muss man nur noch seine Controller Methoden mit einem passenden Attribut versehen z.B. “CreateAngularTsProxyAttribute”. Dann das Projekt kompilieren und das T4 Template “Scripts\ProxyGeneratorScript.tt” ausführen und schon werden die passenden AngularJs Service Funktionen erstellt. Aus dem folgenden .NET Controller

wird dann der volltypisierte AngularJs TypeScript Controller der vom T4 Template generiert wurde.

Das komplette Projekt findet Ihr auf GitHub inklusive einer ausführlichen Beschreibung und ein Beispielprojekt. Das fertige Paket gibt es auch bei NuGet.

Der Proxybuilder kann die folgenden Proxies erstellen, dafür wird jeweils nur das passende Proxybuilder Attribut benötigt:

  • AngularJs pure JavaScript Services
  • AngularJs TypeScript Services voll Typisiert und nur in Verbindung mit TypeLite zu verwenden. TypeLite erstellt Interfaces von unseren .NET Klassen in TypeScript, die der Proxybuilder dann verwendet für Übergabe- und Rückgabewerte.
  • jQuery pure JavaScript Services
  • jQuery TypeScript Services voll Typisiert und nur in Verbindung mit TypeLite.

Wenn Ihr Fragen oder Erweiterungswünsche habt, dann bitte auf GitHub als Issue eintragen.

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.