Template Sets - Lpt templates engine (.lpt templates)
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.
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.
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.