T4 工具箱 - 当前程序集中的引用类

发布于 2024-09-10 17:52:23 字数 152 浏览 3 评论 0原文

我正在编写一个 T4 脚本,它反映某些类并提供基于它们的代码生成。问题是我的脚本出错,说无法访问我当前项目中的类。

该脚本本身与我尝试引用的类驻留在同一程序集中。我尝试引用命名空间、文件并添加对当前程序集(项目本身)的引用 - 一切都无济于事。

我缺少什么?

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 current project cannot be accessed.

The script itself resides in the same assembly as the classes I am trying to reference. I've tried referencing the namespace, the file and adding a reference to the current assembly (the project itself) - all to no avail.

What am I missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

丑疤怪 2024-09-17 17:52:23

我相信这就是 Nicko 和 uosɐſ 正在寻找的。只需将“MyAssembly.CodeGeneration”更改为具有 T4 模板的项目名称即可。

<#@ assembly name="$(TargetPath)MyAssembly.dll" #>
<#@ import namespace="MyAssembly.CodeGeneration" #>

I believe this is what Nicko and uosɐſ are looking for. Just change the "MyAssembly.CodeGeneration" to the name of the project with the T4 templates.

<#@ assembly name="$(TargetPath)MyAssembly.dll" #>
<#@ import namespace="MyAssembly.CodeGeneration" #>
自我难过 2024-09-17 17:52:23

要记住的一件事是,您正在编写的 T4 脚本实际上并不“驻留在同一个程序集中” - 因为它是设计时代码,而不是运行时代码。换句话说 - 它根本不会编译到您的程序集中。

相反,T4 模板脚本会在您编写代码时运行 - 或者,如果您启用了某些挂钩,则每当您构建/编译程序时都会运行。但因为它实际上与您的项目、代码分离,所以它无法直接引用您项目的程序集 - 除非您使用类似 DTE 的东西 - 这使您能够访问 Visual Studio 环境本身,并探索当前加载的项目等元素。

例如,考虑以下脚本:

<#@ 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);
                }
            }
        }
    }

该脚本本质上将运行特定的命名空间(在本例中为 "MyNamespace"),迭代其中的所有类,然后输出新代码文件,仅列出带有 getter/setter 的公共属性 - 本质上,生成对象的 POCO。在我的一些项目中,我使用此代码的改编版本来基于 POCO 生成 JavaScript 对象,以便从序列化的角度来看,我的 JS 模型始终可以与服务器端对象同步。

不过,其技巧在于前几行:

var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
var project = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile).ContainingProject);
var classes = FindClasses(project, targetNamespace, "");

本质上,DTE 服务要求 Visual Studio 为其提供当前加载的解决方案的抽象模型,它是项目。 /代码>。然后,我们加载存储当前 TemplateFileProject,并在 FindClasses() 方法中解析出该项目中的类符合我们的搜索条件。

我希望示例代码为您提供一个起点 - 但如果您需要更多详细信息,这里有一些额外的参考供您深入研究:

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:

诺曦 2024-09-17 17:52:23

由于某种原因,我无法让@brian 解决方案正常工作。我最终这样做了
就我而言,T4Generators 是同一解决方案中的单独项目。

<#@ assembly name="$(SolutionDir)\T4Generators\bin\Debug\T4Generators.dll" #>
<#@ import Namespace="T4Generators" #>

For some reason I couldn't get @brian solution working. I ending up doing this
In my case T4Generators was my seperate project in the same solution.

<#@ assembly name="$(SolutionDir)\T4Generators\bin\Debug\T4Generators.dll" #>
<#@ import Namespace="T4Generators" #>
ㄖ落Θ余辉 2024-09-17 17:52:23

参考常用的方法。然后检查程序集是否已加载,如果没有 - 生成存根代码(以使编译成为可能;编译后再次运行 T4,以生成真实代码)。并进行单元测试,以防止存根代码投入生产。

Reference it common way. Then check if assembly is loaded, if not - generate stub code (to make compilation possible; after compilation run T4 again, to genereate real code). And have unit test that could prevent stub code going to production.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文