Template syntax
editA template is text with code blocks. By default, a code block starts with <% and ends with %>; the character after <% gives its kind:
| Block | Purpose | Example |
|---|---|---|
| Text | Written to the output as is | Hello World |
<%= expression %> | Writes the value of a C# expression | <%= 2 + 2 %> |
<% statements %> | Runs C# statements | <% var x = 10; %> |
<%+ member %> | Declares a member of the generated class | <%+ private static int Get() => 42; %> |
<%@ name value %> | A directive: imports a namespace, references an assembly, … | <%@ using System.IO %> |
<%<% | Writes <% | <%<%= Name %> |
The code is C#, compiled with the latest version of the language that the compiler supports.
Write a value
<%= expression %> writes the value of the expression:
using var template = new Template();
template.Load("2 + 2 = <%= 2 + 2 %>");
var result = template.Run();
Assert.Equal("2 + 2 = 4", result);- A
nullvalue writes nothing. - Nothing is encoded: a value that contains
<or&is written as is. To generate HTML, use HTML emails, whose blocks encode values. - Numbers and dates are formatted with the culture of the writer. Run writes to a
StringWriterthat uses the current culture of the thread; to get the same text on every machine, write to a writer that uses the invariant culture:
using var template = new Template();
template.Arguments.Add(new TemplateArgument("Total", typeof(decimal)));
template.Load("Total: <%= Total %>");
using var writer = new StringWriter(CultureInfo.InvariantCulture);
template.Run(writer, 1234.5m);
Assert.Equal("Total: 1234.5", writer.ToString());Run statements
The code of <% ... %> blocks is pasted, in order, into the body of the method that renders the template, between the instructions that write the text. A block can open a loop or a condition and a later block can close it:
using var template = new Template();
template.Load("""
<% for(int i = 1; i <= 3; i++) { %>
Item <%= i %>: <%= i * 10 %>
<% } %>
""");
var result = template.Run();
Assert.Equal("""
Item 1: 10
Item 2: 20
Item 3: 30
""", result);A statement block can also write to the output directly: the output is a parameter named __output__ (the OutputParameterName property), and __output__.Write(value) writes a value.
Declare members
<%+ ... %> adds fields, properties or methods to the class generated for the template. The template code runs in a static method, so the members it uses must be static:
using var template = new Template();
template.Load("""
<%+private static string Prefix() => "Item"; %>
<%= Prefix() %> 1
""");
var result = template.Run();
Assert.Equal("Item 1", result);The + must follow the delimiter immediately: <% + ... %> is a statement block.
Directives
<%@ name value %> configures the compilation of the template. The name is case-insensitive:
| Directive | Effect |
|---|---|
using | Imports a namespace or declares an alias, like Usings.Add |
reference | References an assembly by path; alias=Name path declares an extern alias |
include | Compiles a C# source file with the template |
inherits | Sets the base class of the generated class (BaseClassFullTypeName) |
implements | Adds interfaces, separated by commas, to the generated class |
Using code and libraries shows each of them. Other directives write nothing and change nothing, but your code can read them from Blocks; the command-line tool reads outputextension this way.
Line breaks
A statement block, a member block or a directive that is alone on its line removes the line break that follows it, so the lines that only hold code do not leave empty lines in the output:
using var template = new Template();
template.Load("""
<ul>
<% foreach (var item in new[] { "Apples", "Pears" }) { %>
<li><%= item %></li>
<% } %>
</ul>
""");
var result = template.Run();
Assert.Equal("""
<ul>
<li>Apples</li>
<li>Pears</li>
</ul>
""", result);- The spaces after the block are removed with the line break, but the spaces before it are kept: put these blocks at the start of their line.
- An expression block (
<%= ... %>) alone on its line keeps its line, because it writes something. - A block that is followed by text on the same line keeps the line break.
Blocks that are not closed
A <% without a matching %> is not an error: the rest of the template, from the <%, is written as text. If a part of your template appears in the output verbatim, look for a missing %>.
Write a delimiter as text
Write the start delimiter twice to write it once as text. The block that would start after it stays text:
using var template = new Template();
template.Load("<%<%= Name %> writes <%= 1 + 1 %>");
var result = template.Run();
Assert.Equal("<%= Name %> writes 2", result);- Each pair writes one delimiter:
<%<%<%<%writes<%<%. A delimiter that is not doubled starts a block, so<%<%<%= 1 %>writes<%1. - The end delimiter needs no escape in text:
%>outside a block is written as is. - Inside a block, the code ends at the first end delimiter, even in a string. Build the delimiter from two strings:
<%= "%" + ">" %>.
The escape sequence is the start delimiter written twice, whatever the delimiters are: with {{ and }}, {{{{ writes {{:
using var template = new Template
{
StartCodeBlockDelimiter = "{{",
EndCodeBlockDelimiter = "}}",
};
template.Load("{{{{Name}} is {{=Name}}");
template.Arguments.Add(new TemplateArgument("Name", typeof(string)));
var result = template.Run("John");
Assert.Equal("{{Name}} is John", result);Change the delimiters
When the default delimiters appear in the text of your template, choose other ones. Set them before calling Load:
using var template = new Template
{
StartCodeBlockDelimiter = "{{",
EndCodeBlockDelimiter = "}}",
};
template.Load("Hello {{=Name}}!");
template.Arguments.Add(new TemplateArgument("Name", typeof(string)));
var result = template.Run("World");
Assert.Equal("Hello World!", result);The character that gives the kind of block (=, +, @) follows the start delimiter, whatever it is. When the text contains the delimiters only a few times, escape them instead of changing them.