Template Sets - Lpt templates engine (.lpt templates)

Changes in v4

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.

Preface

.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.
 
note 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(Of 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.

note Note:
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: 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: 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: 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:\program files\Solutions Design\LLBLGen Pro\RuntimeLibraries\DotNet11\SD.LLBLGen.Pro.ORMSupportClasses.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: 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:
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/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/ASP.NET statement delimiters: <% and %> would match with Code snippets.

Debugging

Because .lpt templates use C#, you have to program the logic to for example determine if an entity is at the FK side of a 1:1 relation inside the C# template code (or use a separate library). This can cause initial bugs and fixing these bugs is easier when a debugger could be attached when the template is ran. The DotNetTemplateEngine has build-in facilities for this. When you want to debug your .lpt template, take the following steps: Be aware that it's not wise to place the Debugger.Break statement inside a loop, as the code generation cycle isn't terminated when you detach the debugger.


LLBLGen Pro v4.2 SDK documentation. ©2002-2014 Solutions Design