【问题标题】:Creating T4 templates at runtime (build-time)?在运行时(构建时)创建 T4 模板?
【发布时间】:2011-01-19 10:30:00
【问题描述】:

我们正在构建一个内部应用程序,它需要生成 HTML 文件以上传到 eBay 列表。我们希望使用模板引擎根据我们预定义的数据库和静态字段生成 HTML 文件。模板还需要具备逻辑能力(if-then、foreach 等)。

我们看过 T4,它看起来很完美,但我们没有看到任何关于它是否具有在运行时使用的功能,以便用户可以创建 T4 模板,然后应用程序可以“编译”它并生成最终的 HTML 文件。这可能吗?如何实现?

如果没有,是否还有其他具有所有这些功能的框架?

【问题讨论】:

    标签: c# .net templates code-generation t4


    【解决方案1】:

    T4 模板可以使用TextTransform.exe 命令行工具进行编译。你 可以让您的应用程序创建一个 .tt 文件,然后调用 TextTransform.exe 来生成输出。

    【讨论】:

    • 这个解决方案不需要在用户的机器上安装Visual Studio吗?这对我们来说是一个限制。
    • Mono 具有相当强大的文本转换实现。如果依赖关系是一个问题,我会看看。
    • 是的,目前,它依赖于 Visual Studio。这将随着 VS2010 / .NET 4 和所谓的预编译 T4 模板而改变。请参阅此处了解信息:olegsych.com/2009/09/t4-preprocessed-text-templates
    【解决方案2】:

    我有一组类似的类用于此,将模板化文本生成嵌入到软件中。

    基本上,它的工作方式类似于旧式 ASP,您将 C# 代码包含在 <%...%> 块中,并且您可以使用 <%= expression %> 发出结果。

    您可以将单个对象传递到模板代码中,当然可以是您喜欢的任何对象类型,或者只是一个参数数组。如果要执行自定义代码,也可以引用自己的程序集。

    这是发出类的样子:

    <%
    var parameters = (string[])data;
    var namespaceName = parameters[0];
    var className = parameters[1];
    %>
    namespace <%= namespaceName %>
    {
        public class <%= className %>
        {
        }
    }
    

    你当然可以循环遍历事物:

    <% foreach (var parameter in parameters) { %>
    <%= parameter %>
    <% } %>
    

    并将代码放入 if 块等中。

    类库在 CodePlex 上发布:

    以及NuGet

    项目自带示例,下载源码或browse it online.

    也可以在这里通过电子邮件回答问题,供其他人查看:

    1. 适合方法调用的所有类型的 C# 代码都可以在模板中编译。它运行正常的 C# 3.5 代码,这意味着没有人为限制。唯一要知道的是,任何包含要发出的模板代码的 if、while、for、foreach 等代码都必须使用大括号,您不能执行单行 if-then 类型块。有关方法调用限制,请参见下文。
    2. data 参数对应于作为参数从您的应用程序传递给.Generate(x) 方法的任何内容,并且属于同一类型。如果您传入您在自己的类库中定义的对象,则需要添加对模板代码的引用才能正确访问它。 (&lt;%@ reference your.class.library.dll %&gt;)
    3. 如果你重用编译后的模板,它本质上只是一个类的方法调用,对.Generate()的实际调用没有额外的开销。如果您不自己拨打.Compile(),第一次拨打.Generate() 将负责处理。另请注意,代码在单独的应用程序域中运行,因此与来回复制参数和结果相关的编组开销很小。但是,代码以正常的 JITted .NET 代码速度运行。

    if-block 示例:

    <% if (a == b) { %>
    This will only be output if a==b.
    <% } %>
    

    对代码格式也没有人为限制,选择最适合你的样式:

    <%
        if (a == b)
        {
    %>
    This will only be output if a==b.
    <%
        }
    %>
    

    请注意,模板的所有非代码部分几乎都将按原样输出,这意味着标签和类似%&gt; 块也将被输出。

    有一个限制,您编写的所有代码都必须适合单个方法调用。

    让我解释一下。

    模板引擎的工作方式是生成一个 .cs 文件并将其提供给 C# 编译器,这个 .cs 文件大致如下所示:

    using directives
    
    namespace SomeNamespace
    {
        public class SomeClass
        {
            public string Render(object data)
            {
                ... all your code goes here
            }
        }
    }
    

    这意味着您不能定义新类、新方法、类级字段等。

    但是,您可以使用匿名委托在内部创建函数。例如,如果您想要一种统一的日期格式:

    Func<DateTime, string> date2str = delegate(DateTime dt)
    {
        return dt.ToString("G");
    };
    

    那么您可以在模板代码的其余部分中简单地使用它:

    <%= date2str(DateTime.Now) %>
    

    我的唯一要求是您不要将文件上传到网络上并声称您编写了代码,除非您可以随意使用它。

    编辑 23.04.2011: 修复了 CodePlex 项目的链接。

    【讨论】:

    • Wooow O.o 我正在下载这个,我会告诉你进展如何。如果它做到了你所说的一切,你将成为一个绝对的救星!
    • 现在编辑了答案,正如您在电子邮件中所说,“数据”变量不存在,因为它被命名为“数据”。它是一个方法参数,因此是小写的“d”。
    • “模板引擎的工作方式是生成一个 .cs 文件并将其提供给 C# 编译器”:这是否也意味着运行它的机器需要 VisStudio 或至少其他东西安装?或者有没有一种方法可以让您以编程方式调用 C# 编译器?我不认为编译器是随 .net 运行时一起提供的......虽然由于 IIS 可以即时编译 ASP.NET,但我想它一定在某个地方......
    • @Lasse 哇,当他们添加该功能时,我想我错过了。谢谢!
    • 要访问命令行外部编译器,您需要安装 SDK,但编译器本身是 .NET 运行时的一部分,除非您安装“客户端配置文件运行时”,我认为缺少编译器。另外,准确地说,我实际上并没有生成 file 本身,我在内存中生成源代码并将其提供给 C# 编译器类,在我询问之前添加对程序集的引用和设置选项它来编译代码。磁盘上没有任何文件,即使程序集是在内存中生成的。
    【解决方案3】:

    实现 T4 文本转换的程序集是 Microsoft.VisualStudio.TextTemplating.dll,它随 Visual Studio 一起提供。

    如果你想从第一原则开始,你需要实现Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost,并将你的实现作为参数传递给Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplate()Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplate()将执行转换。

    与调用 TextTransform.exe 相比,这为您提供了更大的灵活性。

    但是,如果您的代码是发货产品,则不清楚该程序集的许可是什么,以及您是否有权将其与您的应用程序一起重新分发。

    重新分发此程序集将避免安装 Visual Studio。

    【讨论】:

    【解决方案4】:

    完全可以在运行时使用 T4。

    Microsoft 在 .NET 3.5 中并没有以任何合理的方式真正支持这种情况。听起来 .NET 4.0 会得到微软更好的支持。

    Mono 在 .NET 3.5 中确实为这种情况提供了一些支持。

    在 Mono T4 实现的帮助下,我已经在 .NET 3.5 上成功地证明了这个概念,但是针对 .NET 3.5 的这个问题的现成解决方案需要比我迄今为止所投入的更多努力。

    您可以在此处找到 Mono T4 实现:

    https://github.com/mono/monodevelop/tree/master/main/src/addins/TextTemplating

    我在这里记录了尝试从 .NET 代码运行 T4 模板时遇到的一些问题:

    Options for running T4 templates from .NET code

    【讨论】:

      【解决方案5】:

      如果您可以使用 Visual Studio 2010 来创建和编辑模板,那么您可以使用预编译模板,这些模板正是为这种情况而设计的,并且是 Microsoft 支持的选项。

      您在 Visual Studio 中设计模板,对其进行预编译并部署一个不依赖于 Visual Studio 的程序集以及您的应用程序。

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

      【讨论】:

      • 链接已损坏。
      【解决方案6】:

      我犯的一个错误是我添加了一个“文本模板”文件。要在运行时生成文本,请选择“预处理文本模板”。如果您最初选择“文本模板”,则在 VS 的文件属性中将自定义工具设置为“TextTemplatingFilePreprocessor”是一个简单的更改。

      【讨论】:

        【解决方案7】:

        液体可能是一个不错的选择。这是一种开源模板语言,请在此处阅读有关该语言的更多信息: https://shopify.github.io/liquid/

        这是 .NET 的一个实现: https://github.com/dotliquid/dotliquid

        语法非常好。以下是 C# 的一些示例代码:

            class Person
            {
                public string Name { get; set; }
                public int Age { get; set; }
        
                public List<string> Friends { get; set; }
            }
        
            static void Main(string[] args)
            {
                Template.RegisterSafeType(typeof(Person), new string[]
                    {
                        nameof(Person.Name),
                        nameof(Person.Age),
                        nameof(Person.Friends),
                    });
        
                Template template = Template.Parse(
        @"<h1>hi {{name}}</h1> 
        <p>You are{% if age > 42' %} old {% else %} young{% endif %}.</p>
        <p>You have {{ friends.size }} friends:</p>
        {% assign sortedfriends = friends | sort %}
        {% for item in sortedfriends -%}
          {{ item | escape }} <br />
        {% endfor %}
        
        ");
                string output = template.Render(
                    Hash.FromAnonymousObject(
                        new Person()
                        {
                            Name = "James Bond",
                            Age = 42,
                            Friends = new List<string>()
                            {
                                "Charlie",
                                "<TagMan>",
                                "Bill"
                            }
                        } ));
        
                Console.WriteLine(output);
        
        /* The output will be: 
        
        <h1>hi James Bond</h1>
        <p>You are young.</p>
        <p>You have 3 friends:</p>
        
          &lt;TagMan&gt; <br />
          Bill <br />
          Charlie <br />             
        
        */
        
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-12-11
          • 2020-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-28
          • 2011-05-23
          • 1970-01-01
          相关资源
          最近更新 更多