在运行时(构建时)创建T4模板?

23
我们正在开发一个内部应用程序,需要生成HTML文件以上传到eBay列表中。我们希望使用模板引擎来基于预定义的数据库和静态字段生成HTML文件。该模板还需要具备逻辑能力(例如if-then,foreach等)。
我们研究了T4,它看起来非常完美,但我们没有找到关于它是否可以在运行时使用的信息,以便用户可以创建T4模板,然后应用程序可以“编译”它并生成最终的HTML文件。这是可能的吗?如何实现?
如果不行,那么我们应该寻找具有所有这些功能的其他框架吗?
7个回答

16

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

基本上,它的工作方式类似于旧式的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块中放置代码等。

该类库在此发布:

以及在NuGet上。

该项目附带有示例,可下载源代码或在线浏览

为了通过电子邮件回答问题,其他人也可以看到:

  1. 模板中可以编译所有类型的符合方法调用的C#代码。它运行正常的C# 3.5代码,并具有它所包含的所有内容,没有人为的限制。只需知道任何包含发出模板代码的if、while、for、foreach等代码必须使用大括号,不能使用单行if-then类型的块。请参见下面的方法调用限制。
  2. data参数对应于从应用程序传递给.Generate(x)方法的参数,其类型相同。如果您传递了一个在自己的类库中定义的对象,则需要在模板代码中添加对该引用的引用,以便正确访问它。(<%@ reference your.class.library.dll %>
  3. 如果您重复使用编译的模板,则实质上只是调用类的方法,实际调用.Generate()时不会产生额外的开销。如果您没有自己调用.Compile(),则第一次调用.Generate()将处理它。还请注意,代码在单独的appdomain中运行,因此涉及复制参数和结果的轻微编组开销。但是,代码以正常的JITted .NET代码速度运行。

if块的示例:

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

对于格式化代码,没有人为的限制,可以选择最适合您的风格:

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

需要注意的是,模板中所有非代码部分都将几乎原样输出,这意味着在%>块后面的制表符等也将被输出。

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

让我解释一下。

模板引擎的工作方式是生成一个 .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项目的链接。


根据您在电子邮件中提到的,我现在已经编辑了答案。"Data"变量不存在,因为它被命名为"data"。这是一个方法参数,因此小写字母"d"。 - Lasse V. Karlsen
@Lasse:链接似乎不再起作用了。我该如何获取这段代码? - epitka
已发布更新的链接。不幸的是,目前没有二进制存储库,我会考虑发布新的存储库,但由于二进制文件只是主干的构建,所以我决定如果想要恢复二进制支持,必须以更受控制的方式进行。 - Lasse V. Karlsen
如果您有任何问题,请在此处发表评论或发送电子邮件至lasse@vkarlsen.no,让我知道。 - Lasse V. Karlsen
看起来所有的链接又挂了?你还有这些样本在其他地方可用吗? - murki
显示剩余6条评论

14

如果您能够使用Visual Studio 2010进行模板的创建和编辑,那么您可以使用预编译模板,这是专门为此场景设计的并且是Microsoft支持的选项。

您可以在Visual Studio中设计模板,将其预编译并部署一个与Visual Studio无依赖关系的程序集以及您的应用程序。

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


链接已失效。 - Dan

6
实现T4文本转换的程序集是Microsoft.VisualStudio.TextTemplating.dll,该程序集随Visual Studio一起发布。
如果您想从头开始实现,您需要实现Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost并将其作为参数传递给Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplate(),该方法将执行转换。
这比调用TextTransform.exe更具灵活性。
但是,如果您的代码是一个发行产品,不清楚此程序集的许可证情况以及您是否有权将其与应用程序一起重新分发。
重新分发此程序集将避免需要安装Visual Studio。

很遗憾,由于许可问题,这不符合我们的需求,因为该产品将被发送给最终用户。无论如何,谢谢 :) - Amberite
1
还有一个由Mono项目开发的开源实现。 http://anonsvn.mono-project.com/viewvc/trunk/monodevelop/main/src/addins/TextTemplating/ - Filip Frącz

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

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

3

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

在.NET 3.5中,微软并没有以任何合理的方式支持这种情况。听起来.NET 4.0将有更好的支持。

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

我已经成功地证明了这个概念,并得到了Mono T4实现的帮助,但是在.NET 3.5中,一个现成的解决方案需要比我目前投入的更多的努力。

你可以在这里找到Mono T4实现:

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

我在尝试从.NET代码中运行T4模板时遇到了一些问题,我已经记录了其中的一些问题:

从.NET代码运行T4模板的选项


0

对于这个问题,Liquid可能是一个不错的选择。这是一种开源模板语言,可以在此处了解更多关于该语言的信息: 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 />             

*/

    }

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接