【问题标题】:T4 template and run-time parametersT4 模板和运行时参数
【发布时间】:2011-05-23 01:37:44
【问题描述】:

我正在 VS 2010 中构建一个插件,但我卡在了 T4 代。 现在我已经实现(就像 MSDN 建议的那样)一个自定义 T4 主机来生成我的 T4 结果,我以这种方式使用它:

        const string content = @"c:\Simple.tt";
        var engine = new Engine();
        var host = new MyTemplateHost();            
        var result = engine.ProcessTemplate(File.ReadAllText(content), host);
        foreach (CompilerError error in host.Errors)
        {
            Console.WriteLine(error.ErrorText);
        }

在我在模板中传递一个参数之前,这一直有效。一旦我在 .tt 文件中创建了一个参数,主机就吓坏了,说它不知道如何解决它。 我看到你可以使用 TemplateSession 来做到这一点,但我不知道如何将它传递给我的主机? 有没有更好的方法使用 C# 从 .tt 生成代码并在运行时传递参数?也许我走错了路。

【问题讨论】:

    标签: c# visual-studio-2010 t4 template-engine


    【解决方案1】:

    在 Visual Studio 2010 中,T4 模板引擎已彻底改变。 现在您可以直接运行模板文件并将任何参数类型传递给它。

            var template = Activator.CreateInstance<SimpleTemplate>();
            var session = new TextTemplatingSession();
            session["namespacename"] = "MyNamespace";
            session["classname"] = "MyClass";
            var properties = new List<CustomProperty>
            {
               new CustomProperty{ Name = "FirstProperty", ValueType = typeof(Int32) }
            };
            session["properties"] = properties;
            template.Session = session;
            template.Initialize();
    

    此语句将处理以下模板:

    <#@ template language="C#" debug="true"  #>
    <#@ output extension=".cs" #>
    <#@ assembly name="System.dll" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="SampleDomain.Entities" #>
    <#@ parameter name="namespacename" type="System.String" #>
    <#@ parameter name="classname" type="System.String" #>
    <#@ parameter name="properties" type="System.Collections.Generic.List<CustomProperty>" #>
    
    using System;
    using System.Collections.Generic;
    using SampleDomain.Entities;
    
    namespace <#= this.namespacename #>
    {
    public class <#= this.classname #>
    

    老实说,主机不再需要了......

    【讨论】:

    • 这段代码似乎假设使用预处理模板(如果我理解您的类型 SimpleTemplate。请参阅我的答案以了解如何使用内置 VS 主机进行此操作。
    • 非常有帮助,谢谢。也适用于 2012 年。调用 Initialize() 后,您还可以检查 template.Errors.HasErrors 以查看类型是否正确传递:)
    【解决方案2】:

    如果您正在为 VS 构建插件,您可能不需要自定义主机,而是可以通过其服务接口使用内置 VS 主机。

    查看 ITextTemplating 作为核心服务 API,您可以通过将 DTE 对象转换为 IServiceProvider,然后调用 GetService(typeof(STextTemplating)) 来获得它

    要传递参数,您可以将 ITextTemplating 对象旁转为 ITextTemplatingSessionHost,并将 Session 属性设置为 ITextTemplatingSession 的实现。会话本质上只是一个可序列化的属性包。有一个微不足道的提供为TextTemplatingSession

    【讨论】:

    • 谢谢,我明天要去办公室试一试。
    【解决方案3】:

    将 ITextTemplatingSessionHost 添加并实施到您的自定义主机。仅实现 ITextTemplatingEngineHost 不会为您提供会话支持。

     [Serializable()]
        public class CustomCmdLineHost : ITextTemplatingEngineHost,ITextTemplatingSessionHost
        {
    
            ITextTemplatingSession session = new TextTemplatingSession();
    
            public ITextTemplatingSession CreateSession()
            {
                return session;
            }
    
            public ITextTemplatingSession Session
            {
                get
                {
                    return session;
                }
                set
                {
                    session = value;
                }
            }
    

    【讨论】:

      【解决方案4】:

      使用 T4 模板生成运行时

      1. 如果您需要在运行时生成代码,请选择此方法。例如,您想使用 Selenium 生成一个页面对象。

      2. 在您的解决方案中创建一个文件夹,将其命名为 Templates(对于 T4 模板)。

      3. 接下来添加一个 T4 类型的新项目,然后选择 运行时文本模板......我们将模板命名为 MyNodeName.tt,如上图所示。

      4. 如下图添加你的代码,上半部分是Visual Studio插入的...

      您可以看到我们要传入 Namespace 和 ClassName(这些是上面看到的 Model.NameSpaceName 和 Model.ClassName 标记。

      棘手的部分是学习如何传入参数...

      在文件名中创建一个名称为 partial 的新 CS 类。

      但在课堂上不要将其命名为 MyNodeNamePartial,将其命名为 MyNodeName,如下所示:

         public partial class MyNodeName
          {
             public MyNodeNameModel Model { get; set; }
          }
      

      这与 TT 文件的名称相同。 (MyNodeName) 创建它自己的部分类。但现在请注意,我们有一个名为 MODEL 的此类类型的值..

         public class MyNodeNameModel
          {
              public MyNodeNameModel()
              {
                  ClassName = "Test";
              }
              public string ClassName { get; set; }
              public string NameSpaceName { get; set; }
          }
      

      模型类包含 ClassName 和 NameSpaceName 以及您想要“注入”到模板中的任何其他内容。

      如图所示工作的关键, 是使用了运行时文本模板!如果您使用文本模板,无论您做什么,都会看到类似于“找不到模型”的错误或其他模棱两可的问题。

      调试提示: “找不到模型”是 T4 生成代码,它告诉您在带有名为 MODEL 的变量的部分类中,它找不到它!检查您的部分和模型类型,以确保它们与在该文件夹中创建的任何其他普通类命名空间位于相同的命名空间中。

      【讨论】:

        【解决方案5】:

        查看MSDN Reference(“在构造函数中传递参数”一节)。

        总结一下:

        创建一个与您的 TT 文件同名的部分类。

        partial class MyWebPage
        {
            private MyData m_data;
            public MyWebPage(MyData data) { this.m_data = data; }}
        }
        

        然后在类的构造函数中简单地传递你的参数

        MyWebPage page = new MyWebPage(data);
        String pageContent = page.TransformText();
        

        【讨论】:

          【解决方案6】:

          我在这里尝试了所有答案以使其正常工作,但没有一个适合我的方案。最终我将以下内容添加到我的模板中......

          <#@ assembly name="System.CodeDom" #>
          

          .. 然后就成功了

          【讨论】:

            【解决方案7】:

            想通了。有兴趣的请看以下内容:

            http://www.olegsych.com/2009/09/t4-preprocessed-text-templates/

            【讨论】:

            • Oleg 在那个页面上有很多详细信息...你能对你自己的问题发表一个简洁的答案吗?
            • 链接是 404 未找到
            猜你喜欢
            • 2011-04-07
            • 1970-01-01
            • 2011-04-26
            • 2013-12-22
            • 1970-01-01
            • 2011-01-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多