Partials
The {>tag} partial template tag injects a sub-template into the current output. The sub-template is a template string sourced from the bound data or returned by a registered lambda.
Basic syntax
{>BindExpression}The expression resolves to a template string — either a property on the bound object or the return value of a registered lambda. That string is parsed and merged against the same local scope as the partial tag.
Example
Data:
var attendees = new
{
People = new[]
{
new { Id = 1, FirstName = "John", LastName = "Doe" },
new { Id = 2, FirstName = "John", LastName = "Doe" },
new { Id = 3, FirstName = "Jane", LastName = "Smith" }
},
RsvpFormat = "<li><bold>{$.Id}</bold> - {$.LastName}, {$.FirstName}</li>"
};Template:
<ul>
{#each People}
{>$.RsvpFormat}
{/each}
</ul>Result:
<ul>
<li><bold>1</bold> - Doe, John</li>
<li><bold>2</bold> - Doe, John</li>
<li><bold>3</bold> - Smith, Jane</li>
</ul>Scope behavior
A partial inherits the local scope of the tag that invoked it. The partial body sees the same $ and the same parent chain. Walks (..\) and outer-scope variable reads work identically.
To render a partial against a different scope, combine it with {#with}:
{#with Person.Address}
{>$.AddressFormat}
{/with}Inside the partial, local scope is the address object, and walks back lead out through the with-shift to the original parent.
Where partial templates come from
The partial template string can be:
- Authored alongside the data — as in the
RsvpFormatexample above - Loaded at runtime — read from a file, database, configuration system, or CMS, then attached to the bind object before merge
- Selected dynamically — multiple format strings on the bound object, with the template choosing among them via
{#if}or a lambda
Security considerations
Partials with data-bound templates are the engine’s main attack surface for template-driven denial-of-service. The two relevant vectors:
- Recursive partials — a partial whose body invokes itself (directly or transitively) will grow the engine’s scope stack on every invocation. Without a cap, this can crash the host process with a
StackOverflowException(an exception .NET does not allow catching reliably). - Untrusted template content — if partial bodies are user-supplied, they may contain deeply nested blocks or recursive partial chains specifically crafted to exhaust the stack.
The engine enforces a maximum nesting depth (TemplateEngine.MaxStack, currently 64) that bounds total recursion across blocks and partials. Exceeding it throws a catchable TemplateStackDepthException (wrapped in a MergeException), not a stack overflow. See Stack Depth Limit for the full discussion and the threat model it addresses.
Composition pattern
Partials compose well with {#with} for shaping scope before invocation:
Data:
var data = new
{
Customer = new
{
Name = new { First = "John", Last = "Doe" },
Billing = new { Line1 = "123 Main", City = "Dallas", State = "TX", Zip = "75201" },
Shipping = new { Line1 = "456 Oak", City = "Plano", State = "TX", Zip = "75075" }
},
AddressFormat = "{Line1}\n{City}, {State} {Zip}"
};Template:
Billing Address:
{#with Customer.Billing}
{>..\AddressFormat}
{/with}
Shipping Address:
{#with Customer.Shipping}
{>..\AddressFormat}
{/with}Result:
Billing Address:
123 Main
Dallas, TX 75201
Shipping Address:
456 Oak
Plano, TX 75075Lambdas as partial sources
The partial expression can also be a lambda call that returns the template string. This is useful when partial selection depends on runtime conditions:
{>(Kind) => selectTemplate}The selectTemplate lambda receives the kind, returns a template string, and the engine renders it as the partial body. See Lambda Expressions.
Whitespace
{>} is a block-level construct in terms of whitespace handling. It accepts - trim markers ({->expr-}) and is affected by engine-level TrimWhitespace. See Whitespace Control.
Next steps
- Stack Depth Limit — the security cap that makes data-bound partials safe
- With Blocks — pair with partials for reusable scope-shaped fragments
- Lambda Expressions — dynamic partial selection