One thing to keep in mind is that the T4 script you are writing doesn't actually "reside in the same assembly" - because it is design-time code, not run-time code. In other words - it doesn't get compiled into your assembly at all.
Instead, the T4 template script is run while you are writing code - or, if you have certain hooks enabled, whenever you build/compile your program. Because it's actually separate from your project, code, though, it has no ability to reference your project's assembly directly - unless you use something like DTE - which gives you the ability to access the Visual Studio environment itself, and explore elements such as the currently-loaded project.
As an example, consider the following script:
<#@ 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);
}
}
}
}
This script, in essence, will run through a specific namespace (in this case "MyNamespace"
), iterate through all of the classes therein, then output a new code file which lists only their public properties with a getter
/setter
- in essence, producing a POCO of the objects. In some of my projects, I use an adapted version of this code to generate JavaScript objects based on my POCOs, so that my JS models can always be in-sync with my server-side objects, from a serialization perspective.
The trick to it, though, is in the first few lines:
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");
In essence, the DTE service is asking Visual Studio to give it an abstract model of the currently-loaded Solution
and it's Projects
. We then load up the Project
in which the current TemplateFile
is stored, and in the FindClasses()
method, parse out the classes within that project which match our search criteria.
I hope the example code gives you a starting point to jump off from - but if you need further detail, here are a few additional references for you to sink your teeth into: