阿不

潜水

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

T4模板Visual Studio IDE的应用越来越多,现在在VS中,只要与代码生成相关的场景,我们都可以通过修改 T4模板来自定义生成格式,比如MVC的视图模板,Entity Framwork的DataContext模板等等。同时我们还可以自己创建T4模板文件(.tt),使用C#(VB)语法来编写T4模板,它的语法与ASP.NET的语法非常类似,大大降低了.NET程序员的学习成本,关于T4的模板的更多细节请阅读Oleg Sych关于T4的博客文章。

T4模板,除了静态执行输出之外,我们还可以通过程序动态执行并输出。关于动态执行T4模板,可以参考用程序执行 t4 文件Walkthrough: Creating a Custom Text Template Host,这两篇文章。

这两篇文章都讲到,我们要动态执行T4模板,需要自己定义一个ITextTemplatingEngineHost实现类,并在执行时,将它传给T4引擎:

CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
host.TemplateFileValue = templateFileName;
string input = File.ReadAllText(templateFileName);
string output = engine.ProcessTemplate(input, host);

很简单,我们就可以动态执行T4模板。但是,如何传递参数给模板的运行时?以上的介绍中并没有提及,变量是模板引擎的血液,而以上的两篇文章刚好都没有介绍到如何传递变量给T4引擎上下文。花了几个小时,终于在这篇博文中找到一点线索,博文中介绍的是T4中,parameter这个声明的作用和运行原理,它也直接告诉我如何去在运行时传递参数给T4引擎上下文。简单的总结一下:我们可以通过两种方式经T4模板传递上下文,分别是通过CallContext和ITextTemplatingSession对象。通过CallContext传递对象要特别注意的是,当前宿主程序的执行上下文和T4模板引擎上下文是不同的,我们可以通过CallContext.LogicalSetData这个方法来将本地对象传递到另一个上下文使用。另一种办法就是通过传递Session对象的方式,这个Session的概念跟ASP.NET的Session概念很相似,它是一个IDictionary<string,object>对象,它允许我们可以定义一些全局变量,供T4引擎上下文使用。那么,同样的问题是,在宿主上下文中,我们如何将Session传递到T4上下文呢?看这段代码:

var sessionHost = (ITextTemplatingSessionHost) this.Host;
sessionHost.Session = session;

也就是在标准的Host对象实现中,除了实现ITextTemplatingEngineHost,还需实现ITextTemplatingSessionHost这个接口,这个接口就会带有一个Session属性的定义。我们就可以通过给这个Session赋值来达到参数传递的目的。这个设想,我们也可以在Engine的内部代码中得到验证:

private static void InitializeSessionWithHostData(ITextTemplatingEngineHost host, TemplateProcessingSession session)
{
    try
    {
        session.TemplateFile = host.TemplateFile;
    }
    catch (NotImplementedException)
    {
        session.TemplateFile = string.Empty;
    }
    session.IncludeStack.Push(session.TemplateFile);
    ITextTemplatingSessionHost host2 = host as ITextTemplatingSessionHost;
    if (host2 != null)
    {
        session.UserTransformationSession = host2.Session;
    }
}

InitializeSessionWithHostData用于初始化Engine使用的Session数据,它会去检查我们传入的host对象是否实现了ITextTemplatingSessionHost,如有实现,则会将我们传入的Session带到内部去使用。在弄清原理后,我们就可以在Walkthrough: Creating a Custom Text Template Host介绍的自定义ITextTemplatingEngineHost实现类上,再添加实现ITextTemplatingSessionHost接口,然后在执行T4模板时,将需要的参数以Session的方式传给T4引擎使用:

[Serializable]
public class Parameter
{
    public string Name { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        Parameter parameter = new Parameter() { Name = "Name1" };
        CustomTextTemplatingEngineHost host = new CustomTextTemplatingEngineHost();
        host.TemplateFileValue = "test.tt";
        Engine engine = new Engine();

        string input = @"
<#@ template debug=""false"" hostspecific=""false"" language=""C#"" #>
<#@ output extension="".txt"" #>
<#@ parameter name=""parameter1"" type=""T4ParameterSample.Parameter"" #>
test output, paramter name value:<#= parameter1.Name #>

";

        host.Session = new TextTemplatingSession();
        host.Session.Add("parameter1", parameter);

        string output = engine.ProcessTemplate(input, host);

        Console.WriteLine(output);

        foreach (CompilerError error in host.Errors)
        {
            Console.WriteLine(error.ToString());
        }
        Console.ReadLine();
    }
}

以上的实现是通过把参数值放在Session中传递到T4模板上下文中。同时我们也可以CallContext的方式来传递参数:

CallContext.LogicalSetData("parameter1", parameter);

但是不管怎么样,是通过Session还是CallContext,我们的Host实例都是要实现ITextTemplatingSessionHost接口,并且为初始化Session属性。否则在调用this.Session.ContainsKey时就会出现空引用异常,因为它会优先去检查Session中是否有需要的参数值。

另外还有其它的办法,我们也可以达到类似的目的。比如:我们也可以定义自己的“声明”标识,然后通过自己解析这些”声明”,动态生成一些对象实例提供给T4模板使用,但是这种方法更为复杂,需要涉及动态对象生成和代码生成等技术。如有兴趣可参考:Walkthrough: Creating a Custom Directive Processor

最后附上例子

posted on 2010-09-05 13:28  阿不  阅读(7589)  评论(15编辑  收藏  举报