Read classes in a namespace in a t4 template
is there a way I can read in a namespace and loop through all the classes in a t4 template using reflection or something?
<#foreach (class poco in LoadNamespace("Web.Code.Entities.Poco").Classes ) { #>
public interface I<# poco.ClassName #>Repository
{
IQueryable< <# poco.ClassName #> > Get();
<# poco.ClassName #> Save(<# poco.ClassName #> entity);
bool Delete(<# poco.ClassName #> entity);
}
<#} #>
example meta data:
namespace Web.Code.Entities.Poco
{
public class Product
{
public int Id { get; set; }
public string Name{ get; set; }
public string Category{ get; set; }
}
public class Employee
{
public 开发者_如何学Pythonint Id { get; set; }
public string Name{ get; set; }
}
}
desired output:
public interface IProductRepository
{
IQueryable<Product> Get();
Product Save(Product entity);
bool Delete(Product entity);
}
public interface IEmployeeRepository
{
IQueryable<Employee> Get();
Employee Save(Employee entity);
bool Delete(Employee entity);
}
First install T4Toolbox from here
Then In your Template ensure you have the following lines at top :
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ include file="T4Toolbox.tt" #>
Then add these lines of codes:
<#+
private List<CodeClass> FindClasses(string nameSpace, string className)
{
List<CodeClass> result=new List<CodeClass>();
FindClasses(TransformationContext.Project.CodeModel.CodeElements,className,nameSpace,result,false);
return result;
}
private 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);
}
}
}
}
#>
Then you can find all classes in a namespace by calling like this (the second parameter filters all classes which their names contain the passed string) :
FindClasses("NameSpace.SubNameSpace",""))//All classes in the specifed namespace
OR
FindClasses("NameSpace.SubNameSpace","Repository")) //All classes in the specifed namespace which have "Repository" in their names
------------------------------------------ UPDATE FOR VS 2012 --------------------------
Use these functions and namespaces if your T4Toolbox is updated for VS 2012 :
//visual studio 2012+
<#@ assembly name="Microsoft.VisualStudio.Shell.11.0" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="Microsoft.VisualStudio.Shell" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
private List<CodeClass> FindClasses(string nameSpace, string className,string baseClassName)
{
List<CodeClass> result=new List<CodeClass>();
FindClasses(GetProject().CodeModel.CodeElements,className,baseClassName,nameSpace,result,false);
return result;
}
private void FindClasses(CodeElements elements, string className,string baseClassName,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,baseClassName,searchNamespace,result,true);
else
FindClasses(ns.Members, className,baseClassName,searchNamespace,result,false);
}
}
else if(element is CodeClass && isNamespaceOk)
{
CodeClass c = element as CodeClass;
if (c != null)
{
if(c.FullName.Contains(className) && (baseClassName==null || (HasIt(c.Bases ,baseClassName) && c.Name != baseClassName)))
result.Add(c);
FindClasses(c.Members, className,baseClassName,searchNamespace,result,true);
}
}
}
}
private bool HasIt(CodeElements elements,string name)
{
foreach (CodeElement element in elements)
{
if (element.Name==name)
return true;
}
return false;
}
private Project GetProject()
{
// Get DTE
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
return project;
}
private string GetDefaultNamespace()
{
// Get DTE
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
var vsSolution = (IVsSolution)TransformationContext.Current.GetService(typeof(SVsSolution));
IVsHierarchy vsHierarchy;
ErrorHandler.ThrowOnFailure(vsSolution.GetProjectOfUniqueName(project.FullName, out vsHierarchy));
uint projectItemId;
ErrorHandler.ThrowOnFailure(vsHierarchy.ParseCanonicalName(projectItem.FileNames[1], out projectItemId));
object defaultNamespace;
ErrorHandler.ThrowOnFailure(vsHierarchy.GetProperty(projectItemId, (int)VsHierarchyPropID.DefaultNamespace, out defaultNamespace));
return ((string)defaultNamespace);
}
so your search will be something like this :
FindClasses(GetDefaultNamespace(),"Repository","RepositoryBase")
Your best bet is to use the Visual Studio CodeModel, which is part of the DTE automation APIs. You can get hold of the DTE by making your template HostSpecific, casting your Host property to IServiceProvider and then doing a GetService() call.
There's an example of doing this by Colin Eberhardt on CodeProject: http://www.codeproject.com/Articles/39071/Declarative-Dependency-Property-Definition-with-T4.aspx
I am using T4 in a .net core project,
My T4 is fairly large, so just extracting the relevant info here.. [In my case I am looking for Entity Framework Models and I know these are the only classes in the namespace, you may need to filter out your own]
referencing these nuget packages:
<#@ assembly name="$(UserProfile)\.nuget\packages\Microsoft.VisualStudio.TextTemplating.Interfaces.14.0\14.3.25407\lib\net45\Microsoft.VisualStudio.TextTemplating.Interfaces.14.0.dll" #>
<#@ assembly name="$(UserProfile)\.nuget\packages\Microsoft.VisualStudio.TextTemplating.14.0\14.3.25407\lib\net45\Microsoft.VisualStudio.TextTemplating.14.0.dll" #>
these imports/includes:
<#@ import namespace="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating.Interfaces" #>
<#@ include file="$(UserProfile)\.nuget\packages\T4.VsAutomationHelper\1.0.0\tools\ttinc\VsAutomationHelper.CS.ttinclude" #>
<#@ include file="$(UserProfile)\.nuget\packages\T4.TemplateFileManager\2.2.1\tools\ttinc\TemplateFilemanager.CS.ttinclude" #>
I have this method:
// Get all CodeClass Items in specified namespace
public List<EnvDTE.CodeClass> GetClassesInNameSpace(IEnumerable<ProjectItem> items, string nameSpace)
{
var classItems = new List<EnvDTE.CodeClass>();
var csFileProjectItems = items.Where(d => d.Properties.Item("FullPath").Value.ToString().EndsWith(".cs"));
foreach(ProjectItem csFileProjectItem in csFileProjectItems)
{
EnvDTE.FileCodeModel model = csFileProjectItem.FileCodeModel;
foreach(var modelCodeElements in model.CodeElements)
{
if (modelCodeElements is EnvDTE.CodeNamespace)
{
var codeNameSpace = ((EnvDTE.CodeNamespace)modelCodeElements);
if (codeNameSpace.FullName == nameSpace)
{
foreach(var modelCodeElementChild in codeNameSpace.Children)
{
if (modelCodeElementChild is EnvDTE.CodeClass)
{
classItems.Add((EnvDTE.CodeClass)modelCodeElementChild);
}
}
}
}
}
}
return classItems;
}
and call it using: (replace "App.Data.Model.Entities" with your own)
var projectItems = this.dteHelper.GetAllProjectItems();
var entityClasses = GetClassesInNameSpace(projectItems, "App.Data.Model.Entities");
foreach (var entityClass in entityClasses)
{
// entityClass.Name
// Important for .NetCore
// when calling fileManager.StartNewFile() specify all the parameters, its didn't work for me otherwise eg:
// code appreciated (file manager created before following code)
var fileProperties = new FileProperties()
{
BuildAction = BuildAction.Compile
};
fileManager.StartNewFile(dataAccessFileName, generatedFilesTargetProject, generatedFilesTargetFolder, fileProperties);
fileManager.IsAutoIndentEnabled = true;
fileManager.CanOverwriteExistingFile = true;
}
精彩评论