ITemplateLoaderThe include directives requires that a template loader is setup on the TemplateContext.TemplateLoader property
A template loader is responsible for providing the text template from an include directive. The interface of a ITemplateLoader is defined like this:
/// <summary>
/// Interface used for loading a template.
/// </summary>
public interface ITemplateLoader
{
/// <summary>
/// Gets an absolute path for the specified include template name. Note that it is not necessarily a path on a disk,
/// but an absolute path that can be used as a dictionary key for caching). If the loader maps template names to
/// files, it is responsible for validating and normalizing names against its allowed template roots.
/// </summary>
/// <param name="context">The current context called from</param>
/// <param name="callerSpan">The current span called from</param>
/// <param name="templateName">The name of the template to load</param>
/// <returns>An absolute path or unique key for the specified template name</returns>
string GetPath(TemplateContext context, SourceSpan callerSpan, string templateName);
/// <summary>
/// Loads a template using the specified template path/key.
/// </summary>
/// <param name="context">The current context called from</param>
/// <param name="callerSpan">The current span called from</param>
/// <param name="templatePath">The path/key previously returned by <see cref="GetPath"/></param>
/// <returns>The content string loaded from the specified template path/key</returns>
string Load(TemplateContext context, SourceSpan callerSpan, string templatePath);
}
In order to use the include directive, the template loader should provide:
GetPath method translates a templateName (the argument passed to the include <templateName> directive) to a logical/physical path that the ITemplateLoader.Load method will understand.Load method to actually load the the text template code from the specified templatePath (previously returned by GetPath method)The 2 step methods, GetPath and then Load allows to cache intermediate results. If a template loader returns the same template path for a template name any existing cached templates will be returned instead. Cached templates are stored in the TemplateContext.CachedTemplates property.
A template name is application-defined: Scriban passes the value from include to the loader, but it does not normalize or restrict it because a loader might use logical names, database keys, embedded resources, or another non-file scheme. If a loader maps template names to files, the loader should normalize the candidate path and verify that it stays under the intended template root before reading from disk:
/// <summary>
/// A simple ITemplateLoader loading templates from a configured directory.
/// </summary>
public class MyIncludeFromDisk : ITemplateLoader
{
private static readonly StringComparison PathComparison =
OperatingSystem.IsWindows()
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
private readonly string _templateRoot;
public MyIncludeFromDisk(string templateRoot)
{
_templateRoot = Path.GetFullPath(templateRoot);
if (!_templateRoot.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
{
_templateRoot += Path.DirectorySeparatorChar;
}
}
public string GetPath(TemplateContext context, SourceSpan callerSpan, string templateName)
{
var templatePath = Path.GetFullPath(Path.Combine(_templateRoot, templateName));
if (!templatePath.StartsWith(_templateRoot, PathComparison))
{
throw new ScriptRuntimeException(callerSpan, $"Include `{templateName}` is outside the template root.");
}
return templatePath;
}
public string Load(TemplateContext context, SourceSpan callerSpan, string templatePath)
{
// Template path was produced by the `GetPath` method above in case the Template has
// not been loaded yet
return File.ReadAllText(templatePath);
}
}