T4 Toolbox - Referencing Class in Current Assembly

后端 未结 4 1702
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-19 08:48

I am writing a T4 script which reflects over certain classes and provides code generation based on them. The problem is that my script errors out, saying that the classes in my

4条回答
  •  孤独总比滥情好
    2021-02-19 09:36

    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()
               .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 FindClasses(Project project, string ns, string className) {
            List result = new List();
            FindClasses(project.CodeModel.CodeElements, className, ns, result, false);
            return result;
        }
        void FindClasses(CodeElements elements, string className, string searchNamespace, List 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:

    • DTE and T4: Better Together
    • Accessing Visual Studio from a T4 Text Template
    • Accessing Projects via DTE in C# T4 Template

提交回复
热议问题