Template Sets - Lpt templates engine (.lpt templates)

Important!

Starting with v4, .lpt templates have to be written in C#, as they're compiled within a single assembly with all other .lpt templates. Existing .lpt templates, which are written in C#, will pre-select the compiler to use C#. An .lpt template can have any output language, so the template logic has to be in C# but the output language can be anything, e.g. VB.NET or SQL.

.lpt templates are templates written in C# which are parsed and executed by the DotNetTemplateEngine task performer and which result in whatever output is formulated in the templates. As with TDL templates, Lpt (LLBLGen Pro Template) templates are also bound to templateids through templatebindings files.

TDL is the language of choice for the templates which are used to generate the code build on top of the LLBLGen Pro runtime libraries, but .lpt templates are preferred when it it comes to code generation which requires access to the complete project contents and for code generation tasks for which TDL is too limited.

TDL though is powerful with its simplicity: a single statement can represent a lot of logic, logic which would otherwise require a lot of code inside the templates. So if you want fine-grained control, .lpt templates are for you, but at the same time it might require more code to write inside the templates and because of the lower-levelness of the template logic, it might also result in more time being spend on debugging the templates. A facility is build in the DotNetTemplateEngine to make debugging easy.

Generating code works the same as with TDL templates: you select a target platform, language, templategroup and you select a preset which contains the task which uses the DotNetTemplateEngine task performer, or add such a task yourself in the generator configuration dialog. Per generation cycle all templates will be compiled once into a library assembly, which is kept in memory during the generation process. This means that once the assembly is compiled, every task in the generator config will re-use the compiled code.  

Tip

All elements in scope are provided to you through the _executingGenerator (IGenerator) object. This object has basic features you might want to use inside your templates. Additionally, the static class GeneratorUtils is available to you (GeneratorCore namespace) which provides a rich set of functionality which makes writing .lpt templates much easier.

From Template to Class to output

Each template with the extension .lpt defined in a templatebindings file will end up as a class, with the TemplateID as the name of the class. You're free to select which templateID's you use in your templatebindings files, but it is key you don't use spaces in the ID's. When all templates are converted to classes and essential methods and code are added to these classes, the classes are added to a single block of code which is compiled with the C# CodeDom compiler.

Each template class will implement the ITemplateClass interface, defined in the LptParser assembly. This interface defines one method, ___RUN(). ___RUN() is called by the DotNetTemplateEngine for each template to execute. ___RUN() is added to the template class by the engine and takes care of initializing member variables and for calling the __ScriptCode() method. __ScriptCode() is the method to which all script code is added which is found in a template.

___RUN() will receive the active IGenerator instance, the Dictionary<String, TaskParameter> with task parameters, an open StreamWriter object and a reference to the active object (if for example the emitType is set to allEntities, the active object is the current EntityDefinition.) Via the IGenerator instance, you can reach the current Project, all active task definitions, the template set and much more, so you can traverse all objects in the project.

It is recommended you keep the LLBLGen Pro reference manual close or use the Project Inspector plug-in in the LLBLGen Pro designer to check which objects you need / want and how to reach them.

Info

If you want to abort the template execution from within the template itself, throw a TemplateAbortException, defined in the ApplicationCore namespace, which is already available in the template code. The lpt engine will then abort the template and proceed with the next iteration.

Grammar

The objects you can refer to in your templates are:

  • _executingGenerator, which is of type IGenerator, your gateway to the Project object and more. IGenerator is defined in SD.LLBLGen.Pro.GeneratorCore
  • _parameters, which is a Dictionary<string, TaskParameter> with all the parameters defined for the task currently executing, stored with the parameter name as the key.
  • _activeObject, of type object, which is the active object selected in a loop through a set of objects (for example all entities, if emitType is set to allEntities)

There is also an object called __outputWriter, which is the streamwriter. It is not recommended you do anything with that in your templates like closing it but you can use it to call Write() to write text to the output, although it's easier to simply use <%= %>; statements instead

Including an .lpt template into TDL templates

When your .lpt template is used as an include template in a TDL template, _activeObject is a hashtable instead and contains the complete TDL interpreter's state which is calling the .lpt template. Use the following list of keys to find the object you want to work on. The objects passed in are the current scope of the TDL interpreter at the time it calls the .lpt include template.

The hashtable contains for the following keys the following information:

  • CurrentEntity, the entity currently in scope
  • CurrentEntityField, the field currently in scope
  • CurrentEntityFieldRelation, the field-field relationship currently in scope (Pair<IFieldElementCore, IFieldElementCore>. Value1 is pk field, Value2 is fk field)
  • CurrentEntityRelation, the relationship currently in scope
  • CurrentFieldOnRelatedField, the field mapped onto related field (forf) currently in scope
  • CurrentRelatedEntity, the related entity currently in scope
  • CurrentRelatedEntityField, the related entity field currently in scope
  • CurrentSPCall, the sp call currently in scope
  • CurrentSPCallParameter, the sp call parameter currently in scope
  • CurrentTypedListRelation, the typed list relationship currently in scope
  • CurrentTypedList, the typed list currently in scope
  • CurrentTypedListField, the typed list field currently in scope
  • CurrentTypedView, the typed view currently in scope
  • CurrentTypedViewField, the currently set currentTypedViewField
  • CurrentValueType, the value type definition currently in scope

Keys are strings, values are objects or null if not set.

The contents of .lpt templates is considered plain text (which is written to the output without transformation) unless it is placed inside a special block, for example a code block, an assembly declaration or a namespace reference specification. These constructs are described in detail below.

User code regions

To emit user code regions into the output, use the method DotNetTemplateEngine.GetUserCodeRegion(). This method has the following overloads:

  • DotNetTemplateEngine.GetUserCodeRegion(string name, string commentToken);
  • DotNetTemplateEngine.GetUserCodeRegion(string name, string commentToken, string initialRegionContents);
  • DotNetTemplateEngine.GetUserCodeRegion(string name, string commentToken, string endCommentToken, string initialRegionContents, bool emitTrailingCrLf);

The 3rd overload, which accepts an endCommentToken, is useful for output languages which only support comments with an end-marker, like XML or HTML.

Example:

<%=DotNetTemplateEngine.GetUserCodeRegion("Testregion", @"//")%>

Be sure that the name doesn't contain white-space.

Example for XML, which will emit an empty region, and no trailing CRLF after the end marker:

<%=DotNetTemplateEngine.GetUserCodeRegion("Testregion", "<!--", "-->", string.Empty, false)%>

Assembly reference declarations

Syntaxis: <$ full path + filename of assembly $>
Example: <$ c:\myfolder\mydll.dll $>

You need to declare a reference just once in one of the templates in the template set and it is available to all templates.

The following assemblies are already referenced by the code produced by the DotNetTemplateEngine:

  • SD.LLBLGen.Pro.GeneratorCore.dll
  • SD.LLBLGen.Pro.ApplicationCore.dll
  • SD.LLBLGen.Pro.DBDriverCore.dll
  • SD.LLBLGen.Pro.LptParser.dll
  • SD.LLBLGen.Pro.Core.dll
  • SD.Tools.Algorithmia.dll
  • SD.Tools.BCLExtensions.dll
  • mscorlib
  • System.dll
  • System.Core.dll
  • System.Data.dll

A referenced assembly has to be loadable at runtime when the template code is ran. To accomplish this, the referenced assembly has to be in the GAC or has to be placed in a folder which is added to the LLBLGenPro.exe.config's probing tag. If the LLBLGenPro.exe.config file's probing tag doesn't already contain 'ReferencedAssemblies', add it manually. This will make sure the CLR can find the assembly.

Namespace reference declarations

Syntaxis: <[ namespace name ]>
Example: <[ System.Data.SqlClient ]>

You need to declare a reference to a namespace just once in one of the templates in the template set and it is available to all templates.

The following namespaces are already added to the code produced by the DotNetTemplateEngine:

  • System
  • System.IO
  • System.Collections
  • System.Collections.Generic
  • System.ComponentModel
  • System.Linq
  • System.Text
  • System.Data
  • SD.LLBLGen.Pro.GeneratorCore
  • SD.LLBLGen.Pro.ApplicationCore
  • SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData
  • SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData.Tasks
  • SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData.Templates
  • SD.LLBLGen.Pro.ApplicationCore.EntityModel
  • SD.LLBLGen.Pro.ApplicationCore.EntityModel.TypedLists
  • SD.LLBLGen.Pro.ApplicationCore.TypedViews
  • SD.LLBLGen.Pro.ApplicationCore.StoredProcedureCalls
  • SD.LLBLGen.Pro.ApplicationCore.TvfCalls
  • SD.LLBLGen.Pro.ApplicationCore.ProjectClasses
  • SD.LLBLGen.Pro.ApplicationCore.Mapping
  • SD.LLBLGen.Pro.ApplicationCore.MetaData
  • SD.LLBLGen.Pro.ApplicationCore.DerivedModel
  • SD.LLBLGen.Pro.Core
  • SD.LLBLGen.Pro.Core.GeneralDataStructures
  • SD.LLBLGen.Pro.DBDriverCore
  • SD.LLBLGen.Pro.LptParser
  • System.Diagnostics, only when the code generation was started in debug mode.

Template includes

Syntaxis: <# TemplateID #>
Example: <# LPT_GeneralTemplateFuncs #>

Includes are processed prior to template processing and work the same as TDL includes or asp includes. Included templates will also be available in compiled form in the template assembly.

Make sure that the template file bound to the templateID specified have includeOnly="true" specified with their templatebindings in the templatebindings file.

Code snippet

Syntaxis: <% C# code %>
Example:

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

Code between <% and %> will be placed inside the __ScriptCode() method and will thus effectively be executed when a template is executed. Text between %> and <% will be written directly to the output using __outputWriter.Write() statements.

Output code snippet

Syntaxis: <%= C# code %>
Example: <%=GetObjectName()%>

The statement has to be a single statement and should not be appended by a ; when you use C# as the template logic language (e.g.: <%=GetObjectName(); %> will produce a compile error.)

Code block

Syntaxis: <~ C# code ~>
Example:

<~
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";
}
~>

Code blocks are used to define methods in your templates which you can call from your code. You can also define member variables or property declarations with code blocks, it's up to you.

ASP.NET statement

Syntaxis: <%%asp/asp.net code %%>
Example:

<%%=Response.Write("Foo");%%>

which will become in the output:

<%=Response.Write("Foo");%>

The <%% and %%> tokens are necessary because the original ASP.NET statement delimiters: <% and %> would match with Code snippets.