Errors and debugging
editCompilation errors
When the code of a template does not compile, Build (or the first Run) throws a TemplateException. Its message lists the diagnostics of the C# compiler:
using var template = new Template();
template.Load("<%=InvalidExpression%>");
var exception = Assert.Throws<TemplateException>(() => template.Build(CancellationToken.None));
Assert.Contains("InvalidExpression", exception.Message);The positions of the diagnostics are positions in the template, not in the generated code. Set SourceFileName to the path of the template file: the diagnostics start with it, followed by the line and the column of the error, like the errors of a C# file:
using var template = new Template { SourceFileName = "welcome.txt" };
template.Load("""
Hello
<%= Nmae %>
""");
var exception = Assert.Throws<TemplateException>(() => template.Build(CancellationToken.None));
Assert.Contains("welcome.txt(2,5)", exception.Message);
Assert.Contains("CS0103", exception.Message);Errors at run time
An exception thrown by the code of a template reaches the caller of Run wrapped in a TargetInvocationException, because the compiled code is invoked through reflection. The original exception is its InnerException:
using var template = new Template();
template.Load("<% throw new InvalidOperationException(\"Out of stock\"); %>");
var exception = Assert.Throws<TargetInvocationException>(() => template.Run());
var innerException = Assert.IsType<InvalidOperationException>(exception.InnerException);
Assert.Equal("Out of stock", innerException.Message);When SourceFileName is set, the stack trace of the inner exception gives the lines of the template file.
The library throws a TemplateException itself in a few cases: a missing argument when ThrowOnMissingArgument is set (see Passing arguments), and a section that is never ended in an HTML email (see Subject and embedded images).
Inspect the generated code
SourceCode returns the C# code generated from the template. It is set when the template is built, including when the build fails, so you can read the code that the diagnostics refer to:
using var template = new Template();
template.Load("Hello <%=Name%>");
template.Arguments.Add(new TemplateArgument("Name", typeof(string)));
template.Build(CancellationToken.None);
// The generated C# class
var sourceCode = template.SourceCode;
Assert.Contains("public static void Run(dynamic __output__, System.String Name)", sourceCode);Debug a template
Set Debug to compile the template without optimizations, and with the DEBUG symbol instead of RELEASE, so that #if DEBUG blocks of the template are compiled:
using var template = new Template
{
Debug = true,
};
template.Load("<%=Value%>");
template.Arguments.Add(new TemplateArgument("Value", typeof(int)));
var result = template.Run(42);
Assert.Equal("42", result);Debug symbols are generated whatever the value of Debug, and they map the generated code to the lines of the template.