Eine Sache im Auge zu behalten ist, dass die T4-Skript zu schreiben sind nicht wirklich „befinden sich in der gleichen Assembly“ - weil es Design-Time-Code ist, nicht Laufzeitcode. Mit anderen Worten - es wird überhaupt nicht in Ihre Baugruppe kompiliert.
Stattdessen wird das T4-Vorlagenskript ausgeführt, während Sie Code schreiben - oder, wenn Sie bestimmte Hooks aktiviert haben, wenn Sie Ihr Programm erstellen/kompilieren. Da es tatsächlich getrennt von Ihrem Projekt, Code ist, hat es jedoch keine Möglichkeit, die Assembly Ihres Projekts direkt zu verweisen - es sei denn, Sie verwenden so etwas wie DTE - das gibt Ihnen die Möglichkeit, auf die Visual Studio-Umgebung zuzugreifen und Elemente zu erkunden wie das aktuell geladene Projekt.
Als Beispiel betrachten wir das folgende Skript:
<#@ template language="C#" debug="false" hostspecific="true" #>
<#@ output extension=".js" #>
<#@ assembly name="System" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ include file="T4Toolbox.tt" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
string targetNamespace = "MyNamespace";
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");
<# foreach (CodeClass c in classes) { #>
public class <#= c.Name #> {
<# var properties = c.Members.OfType<EnvDTE.CodeProperty>()
.Where(p => p.Access.HasFlag(vsCMAccess.vsCMAccessPublic))
.OrderBy(p => p.Name);
foreach (var prop in properties) {
#>
public <#= prop.Type.AsString #> <#= prop.Name #> { get; set; }
<# } #>
}
<# } #>
<#+ List<CodeClass> FindClasses(Project project, string ns, string className) {
List<CodeClass> result = new List<CodeClass>();
FindClasses(project.CodeModel.CodeElements, className, ns, result, false);
return result;
}
void FindClasses(CodeElements elements, string className, string searchNamespace, List<CodeClass> result, bool isNamespaceOk) {
if (elements == null) return;
foreach (CodeElement element in elements) {
if (element is CodeNamespace) {
CodeNamespace ns = element as CodeNamespace;
if (ns != null) {
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className, searchNamespace, result, true);
else
FindClasses(ns.Members, className, searchNamespace, result, false);
}
} else if (element is CodeClass && isNamespaceOk) {
CodeClass c = element as CodeClass;
if (c != null) {
if (c.FullName.Contains(className))
result.Add(c);
FindClasses(c.Members, className, searchNamespace, result, true);
}
}
}
}
Dieses Skript im Wesentlichen vor, wird einem bestimmten Namespace durchlaufen (in diesem Fall "MyNamespace"
), iterieren darin alle Klassen durch, dann wird ein Ausgang neue Codedatei, die nur ihre öffentlichen Eigenschaften mit einer /setter
auflistet - im Wesentlichen, ein POCO der Objekte zu produzieren. In einigen meiner Projekte verwende ich eine angepasste Version dieses Codes, um JavaScript-Objekte basierend auf meinen POCOs zu generieren, so dass meine JS-Modelle immer serverseitig mit meinen serverseitigen Objekten synchron sein können.
Der Trick, um es, wenn auch ist, in den ersten Zeilen:
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");
Im Wesentlichen der DTE-Dienst Visual Studio fragt ihm ein abstraktes Modell der aktuell geladenen Solution
zu geben und es ist Projects
. Wir laden dann die Project
, in der die aktuelle TemplateFile
gespeichert ist, und analysieren in der FindClasses()
Methode die Klassen innerhalb dieses Projekts, die unseren Suchkriterien entsprechen.
Ich hoffe, dass der Beispielcode gibt Ihnen einen Ausgangspunkt zu springen - aber wenn Sie weiteres Detail benötigen, hier sind ein paar zusätzliche Hinweise für Sie Ihre Zähne in sinken:
Ich habe stattdessen $ (TargetPath) verwendet. Es ist das Makro für die DLL. <# @ Assemblyname = "$ (TargetPath)" #> <# @ import namespace = "MyAssembly.CodeGeneration" #> –
Es wäre nett gewesen, wenn Sie die ursprüngliche Lösung auch verlassen hätten –