Walking the Visual Studio Code Model with T4 Template (Design-Time Reflection)
Date: Friday, February 22, 2013 3:50 PM
Posted by: Nico Ploner
Category: T4
It is common to use Reflection to explore namespaces, types and classes when running in a .NET application. But T4 Templates generate output code during Design Time. So there are no compiled assemblies yet that could be reflected or that ones available are from the last build and outdated.
In order to get information on what classes will be available in your assembly to be used inside a T4 Template, you need to access the Visual Studio CodeModel of the current project that is available at Design Time.
Access the Code Model
Using the Visual Studio Code Model you can access the following artifacts of your code at Design Time:
Types, Namespaces, Structures, Interfaces, Classes, Enumerations, Variables, Delegates, Properties and Functions. These Artifacts can be accessed via the CodeModel-Property of a EnvDTE.Project class or the Property “FileCodeModel” of a EnvDTE.ProjectItem.
The next steps show you, what template directives are necessary to access the CodeModel, how to get all namespaces inside a Visual Studio Project and how to browse those namespaces.
Basics
To get access to the Visual Studio Automation Model you need to add a reference to the EnvDTE.dll. So add the following line to the existing template directives:
<#@ assembly name="EnvDte" #>
Now you can access the running instance of Visual Studio using the template Host as a ServiceProvider and receive the current Project via the fileName of the T4 template file:
<#
var visualStudio = (this.Host as IServiceProvider).GetService(typeof(EnvDTE.DTE))
as EnvDTE.DTE;
var project = visualStudio.Solution.FindProjectItem(this.Host.TemplateFile)
.ContainingProject as EnvDTE.Project;
#>
Get namespaces
From here on it would be straight forward to iterate the project.CodeModel collection of CodeElements to find all internal and external namespaces used in that project. But here is a known Visual Studio bug: All namespaces retrieved by the project.CodeModel collection are marked as external. This prevents us from isolating all namespaces within the project.
That’s why we need to use the FileCodeModel as a workaround instead, which is significantly slower than the CodeModel.
We need to recursively get all project items of the project and use their file code model to get the project’s namespaces. (Please note, that the Method GetProjectItemsRecursively(…) resides inside a <#+ #> class code block you should position at the end of your template file)
<#+
public List<EnvDTE.ProjectItem> GetProjectItemsRecursively(EnvDTE.ProjectItems items)
{
var ret = new List<EnvDTE.ProjectItem>();
if (items == null) return ret;
foreach(EnvDTE.ProjectItem item in items)
{
ret.Add(item);
ret.AddRange(GetProjectItemsRecursively(item.ProjectItems);
}
return ret;
}
#>
Now call that method in the template code and find all namespaces inside the project items’ FileCodeModel:
<#
var namespaces = new List<string>();
foreach(EnvDTE.ProjectItem item in GetProjectItemsRecursively(project))
{
if (item.FileCodeModel == null) continue;
foreach(EnvDTE.CodeElement elem in item.FileCodeModel.Elements)
{
if (elem.Kind == EnvDTE.vsCMElement.vsCMElementNamespace
&& !namespaces.Contains(elem.FullName))
namespaces.Add(elem.FullName);
}
}
#>
Now that we know what namespaces have been defined inside the project, we can use the project’s CodeModel to find those namespaces there:
<#
var codeNamesapces = project.CodeModel.CodeElements
.OfType<EnvDTE.CodeNamespace>()
.Where(cn => namespaces.Contains(cn.FullName);
#>
Browse namespaces
In order to find Artifacts inside the codeNamespaces, use the Members-Property. In all other cases use the Children-Property.
The following code snippet generates a list of all class names in all Namespaces of the current project:
<#
foreach(EnvDTE.CodeNamespace ns in codeNamespaces)
{
foreach(EnvDTE.CodeClass cc in ns.Members.OfType<EnvDTE.CodeClass>())
{
#><#= cc.FullName #>
<# }
}
#>
Download from our T4 Gallery
There is a full sample available for download from our T4 Template Gallery. This template walks all CodeElements in the Visual Studio Solution and displays them hierarchically in a Html TreeView just like the Visual Studio Object Browser.
Example
Christian Weyer shows in his blog an application of using the Code Model and generates WCF operation contract signatures: