Using code and libraries
editA template is compiled on its own, with a small set of references and namespaces. This page shows how to give it access to more code. Each setting exists as a property of Template, for the code that loads the template, and as a directive, for the author of the template.
Import namespaces
Templates import System, System.Collections.Generic, System.Linq, System.Text and System.Threading.Tasks by default:
using var template = new Template();
template.Load("<%= Enumerable.Range(1, 5).Sum() %>");
var result = template.Run();
Assert.Equal("15", result);Add a namespace to Usings to use its types without their full name. Importing a namespace that is already imported has no effect:
using var template = new Template();
template.Usings.Add("System.IO");
template.Load("<%= Path.GetExtension(\"report.txt\") %>");
var result = template.Run();
Assert.Equal(".txt", result);An entry can also declare an alias:
using var template = new Template();
template.Usings.Add("SB = System.Text.StringBuilder");
template.Load("<% var sb = new SB(); sb.Append(\"Hello\"); %><%= sb.ToString() %>");
var result = template.Run();
Assert.Equal("Hello", result);In the template, the using directive does the same:
using var template = new Template();
template.Load("""
<%@ using System.IO %>
<%= Path.GetFileNameWithoutExtension("report.txt") %>
""");
var result = template.Run();
Assert.Equal("report", result);Reference assemblies
By default, a template references the core library, LINQ, the collections, the console, the Meziantou.Framework.Templating assembly and the assembly of its OutputType. To use the types of another assembly, add it to AssemblyReferences, from a type, an assembly, a module or a path. An alias declares an extern alias:
using var template = new Template();
template.AssemblyReferences.Add(typeof(string));
template.AssemblyReferences.Add(typeof(Template).Assembly, "TemplatingAlias");
template.AssemblyReferences.Add(typeof(Template).Module);
template.AssemblyReferences.Add(@"c:\libs\other-library.dll");
template.IncludedSourceFiles.Add(@"c:\templates\shared-helpers.cs");IncludedSourceFiles adds C# files to the compilation of the template. In the template, the reference and include directives do the same; the value of reference can start with alias=Name:
using var template = new Template();
template.Load("""
<%@ reference alias=MyAlias c:\libs\my-library.dll %>
<%@ include c:\templates\shared-helpers.cs %>
""");
var directives = template.Blocks.OfType<DirectiveBlock>().ToList();
Assert.Equal("reference", directives[0].Name);
Assert.Equal(@"alias=MyAlias c:\libs\my-library.dll", directives[0].Value);
Assert.Equal("include", directives[1].Name);
Assert.Equal(@"c:\templates\shared-helpers.cs", directives[1].Value);- The paths are read when the template is compiled. A relative path is resolved from the current directory of the process: prefer full paths.
- An included file is compiled as C#, not as a template: it can declare helper classes for the template, but it writes nothing.
- The types of arguments need a reference too. See Passing arguments.
Share helpers between templates
A member block (<%+ ... %>) declares a helper for one template. To share helpers between templates, declare them in a public base class:
public abstract class ReportTemplateBase
{
protected static string Bullet(string text) => "- " + text;
}Set it as the base class of the generated class, and reference its assembly:
using var template = new Template();
template.AssemblyReferences.Add(typeof(ReportTemplateBase));
template.BaseClassFullTypeName = typeof(ReportTemplateBase).FullName;
template.Load("<%= Bullet(\"Apples\") %>");
var result = template.Run();
Assert.Equal("- Apples", result);- The template code runs in a static method: the helpers must be
static, and can beprotected. - The base class must be public, not nested and not generic, for the same reason as the types of arguments.
- In the template, the
inheritsdirective sets the base class, andimplementsadds interfaces to the generated class (ImplementedInterfaces).
Read your own directives
A directive that is not built in has no effect, but it is kept in Blocks with its name and its value. Use it to store settings in the template itself, such as the extension of the file to generate:
using var template = new Template();
template.Load("""
<%@ outputextension .cs %>
public class Generated { }
""");
var directive = template.Blocks.OfType<DirectiveBlock>()
.Single(block => string.Equals(block.Name, "outputextension", StringComparison.OrdinalIgnoreCase));
Assert.Equal(".cs", directive.Value);
Assert.Equal("public class Generated { }", template.Run());The built-in directives are applied when the template is compiled. To give a directive of your own an effect at that time, derive from Template, override CreateDirectiveBlock, and return a DirectiveBlock that overrides ApplyDirective.