使用与模板存在于同一项目中的 T4 模板中的类型

Posted

技术标签:

【中文标题】使用与模板存在于同一项目中的 T4 模板中的类型【英文标题】:Using types in a T4 template that exist in the same project as the template 【发布时间】:2011-03-25 09:40:19 【问题描述】:

我正在开发我的第一个 T4 代码生成工具,以便将一些存储过程帮助程序代码添加到我的项目中。我创建了自定义类型(例如 StoredProcedureStoredProcedureParameter 来帮助我生成代码,并在我的代码中包含了程序集和命名空间引用:

<#@ template debug="false" hostspecific="false" language="VB" #>
<#@ output extension=".generated.vb" #>
<#@ assembly name="$(TargetPath)" #>
<#@ import namespace="StoredProcCodeGenerator" #>

这允许我在我的 T4 模板代码中使用我的自定义类型。但是,因为我的自定义类型与 T4 模板代码存在于同一个项目中,所以在不重新启动 Visual Studio 的情况下运行模板代码后,我无法重新编译我的项目。这不是很有趣。

我阅读了great article,它通过使用 T4 工具箱解决了这个确切的问题,但它不起作用。要么我执行 VolatileAssembly 指令错误,要么根本没有安装 T4 工具箱。我不确定工具箱是否安装正确(我在 Win XP 上使用 VS 2010)。

有哪些方法可以解决这个问题?

【问题讨论】:

我不明白。在 VS2010 中,我一直使用 T4 模板,包括使用与模板在同一个项目中的类型,它工作得很好,并且每当我保存时都会重新运行模板——就像我所期望的那样。 @Kirk 我没有意识到在添加&lt;#@ VolatileAssembly ... 之前我必须删除&lt;#@ assembly name="$(TargetPath)" #&gt;。我添加了一个答案来解释它。 有人可以在开头编辑标题以说模板而不是模板吗? @马斯洛哈!很棒的收获。我可能已经从标题开始了 50 次,但从未注意到这一点。谢谢! 【参考方案1】:

您需要删除之前的assembly 引用并然后添加VolatileAssembly 引用。如果您不先删除常规的 assembly 引用,则在添加 VolatileAssembly 引用时会收到一个错误,指出它已添加。

<#@ template debug="false" hostspecific="false" language="VB" #>
<#@ output extension=".generated.vb" #>

&lt;#@ assembly name="$(TargetPath)" #&gt;

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" 
    name="$(TargetPath)" #>
<#@ import namespace="StoredProcCodeGenerator" #>  

现在您可以继续构建您的项目并在您的 T4 模板中使用该项目中定义的类型。

【讨论】:

两者都没有会发生什么? (我既没有“程序集”也没有“VolatileAssembly”,并且项目中包含 .tt 文件的类始终可供我访问。) @Kirk 这很有趣。如果我不包含程序集引用,我会收到一个错误,指出它找不到我的自定义类型。例如,我在 .tt 文件中使用了一个 StoreProcedure 类型。如果我不引用我的程序集,它将无法找到 StoredProcedure 类型。 Kirk,你可能在使用 VS2008 吗? 2008 年,我们也从项目引用中引入了本地类型作为标准搜索。我们在 2010 年将其移除,以便您的 T4 世界与您实际构建的世界隔离开来。我们这样做是为了在模板中使用 .Net 4.0 时支持定位 .Net 2.0 或 3.5 项目。【参考方案2】:

希望这是有帮助的,它显示了一个使用 volatileAssembly 的用例,我完全没有运气让这个 t4 模板工作,但我认为它可能会有所帮助:

// <autogenerated/>
// Last generated <#= DateTime.Now #>
<#@ template language="C#" hostspecific="true"#>

<#@ assembly name="System" #>

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkWpf.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkTestToolkit.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\WpfAppTemplate.exe" #>

<#@ output extension=".cs" #>

<#@ import namespace="System" #>
<#@ import namespace="FrameworkTestToolkit" #>

namespace WpfAppTemplateTest 
 using System;
 using System.Reflection;
<# 
    // Add new types into the below array:
    Type[] types = new Type[]  
 typeof(FrameworkWpf.SafeEvent),
 typeof(FrameworkWpf.Mvvm.ControllerBase),
 typeof(FrameworkTestToolkit.PrivateAccessorGeneratorTestClass),
 typeof(WpfAppTemplate.PostController),
 typeof(WpfAppTemplate.ShellController),
 ;


 // Do not modify this code
 foreach (Type type in types) 
 PrivateAccessorGenerator builder = new PrivateAccessorGenerator(type, WriteLine, Error, Warning);
 builder.Generate();
 
#>

来自http://blog.rees.biz/Home/unit-testing-and-private-accessors2

【讨论】:

【参考方案3】:

您也可以使用EnvDte 遍历代码:

        <#@ template language="C#" hostspecific="True" debug="True" #>
    <#@ output extension="cs" #>
    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Xml" #>
    <#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
    <#@ assembly name="EnvDTE" #>
    <#@ assembly name="EnvDTE80" #>
    <#@ assembly name="VSLangProj" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Text.RegularExpressions" #>
    <#@ import namespace="System.Xml" #>
    <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
    <#@ import namespace="EnvDTE" #>
    <#@ import namespace="EnvDTE80" #>
    <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#
    var serviceProvider = Host as IServiceProvider;
        if (serviceProvider != null) 
            Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
        

        // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
        if (Dte == null) 
            throw new Exception("T4Generator can only execute through the Visual Studio host");
        

        Project = GetProjectContainingT4File(Dte);

        if (Project == null) 
            Error("Could not find the VS Project containing the T4 file.");
            return"XX";
        

        AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
        RootNamespace = Project.Properties.Item("RootNamespace").Value.ToString();

        Console.WriteLine("Starting processing");
        ProcessFileCodeModel(Project);
    #>

我已经在http://imaginarydevelopment.blogspot.com/2010/11/static-reflection-or-t4-with-envdte.html发布了更多以此为基础的代码

【讨论】:

但这只是将 RootNamespace 作为字符串文字。您如何将其用作指令?

以上是关于使用与模板存在于同一项目中的 T4 模板中的类型的主要内容,如果未能解决你的问题,请参考以下文章

多个项目中的多文件项模板

MSBuild 支持 Visual Studio 2017 RTM 中的 T4 模板

T4Toolbox简单了解

真实项目中VS2015中自建T4模板生成文件的使用

VS2022 可扩展性:如何解决“在 T4 模板执行中,‘Assembly 1’和‘Assembly2’中都存在类型‘XXX’

如何从 PowerShell 脚本触发 T4 模板