Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read classes in a namespace in a t4 template

Tags:

t4

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 int 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);
    }
like image 518
eiu165 Avatar asked Oct 13 '10 14:10

eiu165


3 Answers

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;
    }
like image 122
Mark Redman Avatar answered Nov 15 '22 16:11

Mark Redman


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")
like image 29
Mahmoud Moravej Avatar answered Nov 15 '22 16:11

Mahmoud Moravej


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

like image 2
GarethJ Avatar answered Nov 15 '22 16:11

GarethJ