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 RsvpFormat example 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 75075

Lambdas 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