Template Studio 模板开发入门 Lpt templates Development

作为一款优秀的ORM工具,一直享受着LLBL Gen快速代码生成的好处,却很少钻研它的原理,趁项目不忙,研究下LLBL Gen 3.x的模板开发方法。如果不熟悉基础的配置和步骤,请参考文章《优秀的基于模板的代码生成工具Template Studio》。LLBL Gen提供的ORM框架是免费的,源代码也可以从官网获取,ORM设计器需要商业许可。

既然是入门,肯定不能太复杂,参考Code Smith的入门资料的例子,生成下面的文件,作为模板代码生成的入门例子。

using System;
namespace <%=NameSpace %>
{
     /// <summary>
     /// Summary description for <%=ClassName %>.
     /// </summary>
      public class <%=ClassName %>
      {
            public <%=ClassName %>()
            {
                 //
                 // TODO: Add constructor logic here
                 //
            }
      }

}

打开LLBL Gen的设计器,加载一个项目,点击菜单Windows->Show Template Bindings Viewer,调出Template Studio。注意,LLBL Gen 2.x的模板工具是独立的程序,称作Template Studio,LLBL Gen 3.x将模板工具直接集成到LLBL Gen设计器中,方便使用。新建立一个Template Bindings,界面如下图所示

image

添加两个模板Entity和EntityProperty,放在ISLTemplate目录下面,书写模板和模板生成的代码的语言都是C#。

CTRL+E,编辑模板Entity,因为是新增加的模板,会提示是否创建模板文件,选择Yes,Template Sutdio会创建模板文件,并打开它,供编辑模板,请添加如下的模板代码

<%
Project currentProject = _executingGenerator.ProjectDefinition;
EntityDefinition currentEntity = (EntityDefinition)_activeObject;

%>
using System;
using System.Data;
using <%=_executingGenerator.RootNamespaceToUse%>;

namespace <%=_executingGenerator.RootNamespaceToUse%>
{
      /// <summary>
      /// Summary description for <%=currentEntity.Name%>.
      /// </summary>
      public class <%=currentEntity.Name%>
      {
            public <%=currentEntity.Name%>()
            {
                  //
                  // TODO: Add constructor logic here
                  //
            }
      }
}

如果有写过T4或是Code Smith模板的经验,这些经验仍然可以派上用场。不同的模板生成工具,不同的方面是获取元数据的方法,先在Visual Studio中写好最终需要的代码,然后把变化的部分用元数据替换。

<% 和 %> 包括的部分是代码片段,在运行时会执行。_executingGenerator是Template Studio内置的变量,在运行时动态传入值,它的类型是IGenerator,定义于程序集SD.LLBLGen.Pro.GeneratorCore中。还有两个变量

_parameters, 类型是Dictionary(String, TaskParameter) ,存放当前task的参数

_activeObject, 如果emitType=allEntites,则它代表当前的实体对象

这样解释感觉不太明朗,没有说透。以Entity模板为例子,模板引擎会为它生成一个类型,代码如下

public class Entity : ITemplateClass {
    private StreamWriter __outputWriter;
    private IGenerator _executingGenerator;
    private Dictionary<string, TaskParameter> _parameters;
    private object _activeObject;

    public Entity() {
        __outputWriter=null;_executingGenerator=null;_parameters=null;_activeObject=null;
    }

    private void __ScriptCode() {

Project currentProject = _executingGenerator.ProjectDefinition;
EntityDefinition currentEntity = (EntityDefinition)_activeObject;

       __outputWriter.Write("\r\nusing System;\r\nusing System.Data;\r\n");
       __outputWriter.Write("using ");
       __outputWriter.Write(_executingGenerator.RootNamespaceToUse);
       __outputWriter.Write(";\r\n\r\nnamespace ");
       __outputWriter.Write(_executingGenerator.RootNamespaceToUse);
       __outputWriter.Write("\r\n{\r\n      /// <summary>\r\n      /// Summary description for ");
       __outputWriter.Write(currentEntity.Name);
       __outputWriter.Write(".\r\n      /// </summary>\r\n      public class ");
       __outputWriter.Write(currentEntity.Name);
       __outputWriter.Write("\r\n      {\r\n            public ");
       __outputWriter.Write(currentEntity.Name);
       __outputWriter.Write("()\r\n            {\r\n     //\r\n   // TODO: Add constructor logichere\r\n           //\r\n            }\r\n            \r\n");
        __outputWriter.Write("\r\n\r\n           ");
        __outputWriter.Write("\r\n\r\n");
        __outputWriter.Write("\r\n//");
        __outputWriter.Write("\r\n\r\n\r\n");
        __outputWriter.Write("\r\n      }\r\n}");
    }

    public void ___RUN(IGenerator executingGenerator, Dictionary<string, TaskParameter> parameters,   StreamWriter outputWriter, object activeObject) {
        __outputWriter = outputWriter; 
        _parameters = parameters;   
        _executingGenerator=executingGenerator;
        _activeObject = activeObject;
        __ScriptCode();
    }

}

模板引擎会运行生成的___RUN方法,传放当前的项目信息(executingGenerator包含项目信息),和task的参数,如果是多次执行,将当前的对象赋给activeObject ,一并传入lpt模板。模板引擎分析lpt模板时,如果是字符串则直接用变量__outputWriter输出,如果是变量,比如<%=currentEntity.Name%>,则取它的值。

做基于LLBL Gen的代码生成器,所需要元数据就都在以上这四个变量中,如果不熟悉它的属性,可以用Reflector查看。

 

Entity模板设计好后,还需要设计它的传入参数。因为是自定义的模板,点击Add Tasks,添加ConsumeLptTemplate

image

再来配置它的几个参数。destinationFolder是存放模板的路径,相对于在General settings中设定的Destination root目录,可以使用DOS命令中的..和./表示当前目录或上一层目录,也可以放空,表示Destination root目录。如果它的值不为空,比如为GUI或是默认的值FolderName,则要添加一个DirectoryCreator任务,先产生目录,再来生成代码。
设置emitType=allEntities,因为这里是要为所有的实体生成一个类型文件,如果只为部分实体生成类型文件,请依照下图,来勾选需要生成代码的类型。

emitType的其它几个值,allTypedLists是所有的类型列表,allTypedViews所有视图,actionSPCalls所有命令存储过程,retrievalSPCalls所有查询存储过程,generic只执行一次

templateID设置为Entity,以表示当前任务,要使用的模板名称。filenameFormat默认为[elementName].[extension],可不作修改,elementName是实体,extension依据当前要生成代码的项目类型,可以是VB或CS。

其它的选项可不作修改,最终的配置效果如图所示

image

点击 Start generator,就会在Destination root目录中生成代码,对每一个实体,生成一个类型文件。

 

前面已经提到过,要加入一些代码,作出逻辑判断,可以用<%和%>包括起来,这种情形用于修改元数据的值。

和ASP.NET相似,<%和%>括起工代码片段,,<%=%>,中间的部分是表达式,返回字符串

比如元数据提供的类型名称是Employee,而我需要的字符串是IEmployee,则可以用这种方法
public class I<%=currentEntity.Name%>  {             }

C#或VB代码片段的例子如下

<%
for(int i=0;i<_executingGenerator.ProjectDefinition.Entities.Count;i++)
{
       %>  some text which will end up Count times in the output <%
}
%>


如果做过LLBL Gen的开发,对__LLBLGENPRO_USER_CODE_REGION不会陌生。代码生成器在重新生成代码时,会保留这个标记符号中间的代码,而不会出现代码丢失。所以,要修改代码生成器生成的代码,增加一些业务逻辑,也应该遵守这个规则,把代码放到这两个符号级中间

// __LLBLGENPRO_USER_CODE_REGION_START Entity Property 
// __LLBLGENPRO_USER_CODE_REGION_END

要生成这种符号标记,应该用这样的代码
<%=DotNetTemplateEngine.GetUserCodeRegion("Entity Property", "//")%>

 

LLBL Gen的模板允许嵌套,也就是一个模板文件中,可以嵌套定义多个子模板。以开头的两个模板Entity和EntityProperty模板为例子,要将EntityProperty模板嵌入到Entity模板中,应该这样写

<# EntityProperty #>

这令我想起了ASP和ASP.NET时代的页面嵌套,很相似。

 

使用<%和%>包括起来的代码片段部分,也可以包含方法定义,然后再在合适的地方,调用方法,请参考如下例子

<~
public string GetObjectName()
{
    if(_activeObject==null)
    {
        return "no active object";
    }
    EntityDefinition e = _activeObject as EntityDefinition;
    if(e!=null)
    {
        return e.Name;
    }
    TypedListDefinition tl = _activeObject as TypedListDefinition;
    if(tl!=null)
    {
        return tl.Name;
    }
    TypedViewDefinition tv = _activeObject as TypedViewDefinition;
    if(tv!=null)
    {
        return tv.Name;
    }
    SPCallDefinition sc = _activeObject as SPCallDefinition;
    if(sc!=null)
    {
        return sc.Name;
    }
    // unknown object
    return "Unknown";
}
~>

//<%=GetObjectName()%>

如果方法定义于一个外部程序集中,可以用这种方法引用外部程序集,用<[和]>括起来,例子如下

<[ System.Data.SqlClient ]>


如果要输出ASP.NET的类型变量,比如输出Foo,用C#是这样写:Response.Write("Foo");
转换成LLBL Gen模板语法:<%%=Response.Write("Foo");%%>

如果需要调试模板,参数设置debugBuild=true,在模板的开头添加<[ System.Diagnostics ]>,然后在需要停下来的地方,添加代码<% //  Debugger.Break(); %>,这样在运行模板时,生调出窗口,选择调试器,以让调试器跟踪到生成的代码中去。不过,这种方法测试过多次,也没有成功调试。

LLBL Gen的模板编辑器,没有智能提示功能,只是个语法高亮的文本编辑器,LLBL Gen 3.x也没有提供对模板的编译和语法检查,2.x有语法检查功能,确实不好用。我想到的办法是,用Visual Studio来写模板代码,然后拷贝到Template Bindings Viewer编辑器中,对于错误模板的诊断和分析,只有靠经验来判断,这会令我无限的怀恋Visual Studio的功能,边写代码,后台自动检测语法错误。也许这是一个契机,开发一款灵活好用的LLBL Gen的模板编辑器

posted @ 2011-09-01 09:05  信息化建设  阅读(2130)  评论(2编辑  收藏  举报