Script Blocks

Script Blocks lets you define reusable statements that can be invoked with a new context allowing the creation custom iterators and helpers - making it easy to encapsulate reusable functionality and reduce boilerplate for common functionality.

Default Blocks

name body
noop verbatim
with default
if default
while default
raw verbatim
function code
defn lisp
capture template
markdown verbatim
csv verbatim
partial template
html template

ServiceStack Blocks

name body
minifyjs verbatim
minifycss verbatim
minifyhtml verbatim
svg template

Script Block Body

The Body of the Script Block specifies how the body is evaluated. Script Blocks can be used in both #Script Template and Code Statement Blocks.

Below are examples of using a script block of each body type in both #Script template and code statement blocks:

default

If unspecified Script Blocks are evaluated within the language they're used within. By default #Script pages use template expression handlebars syntax:

{{#if test.isEven() }}
    {{test}} is even
{{else}}
    {{test}} is odd
{{/if}}

Whilst in Code Statement Blocks the body is executed as JS Expressions, which requires using quoted strings or template literals for any text you want to emit, e.g:

```code
#if test.isEven()
    `${test} is even`
else
    `${test} is odd`
/if
```

verbatim

The contents of verbatim script blocks are unprocessed and evaluated as raw text by the script block:

{{#csv cars}}
Tesla,Model S,79990
Tesla,Model 3,38990
Tesla,Model X,84990
{{/csv}}
```code
#csv cars
Tesla,Model S,79990
Tesla,Model 3,38990
Tesla,Model X,84990
/csv
```

template

The contents of template Script Blocks are processed using Template Expression syntax:

{{#capture out}}
    {{#each range(3)}}
    - {{it + 1}}
    {{/each}}
{{/capture}}
```code
#capture out
    {{#each range(3)}}
    - {{it + 1}}
    {{/each}}
/capture
```

code

The contents of code Script Blocks are processed as JS Expression statements:

{{#function calc(a, b) }}
    a * b |> to => c
    a + b + c |> return
{{/function}}
```code
#function calc(a, b)
    a * b |> to => c
    a + b + c |> return
/function 
```

lisp

Finally the contents of lisp Script Blocks is processed by #Script Lisp:

{{#defn calc [a, b] }}
    (def c (* a b))
    (+ a b c)
{{/defn}}
```code
#defn calc [a, b]
    (def c (* a b))
    (+ a b c)
/defn
```

Syntax

The syntax for blocks follows the familiar handlebars block helpers in both syntax and functionality. #Script also includes most of handlebars.js block helpers which are useful in a HTML template language whilst minimizing any porting efforts if needing to reuse existing JavaScript handlebars templates.

We'll walk through creating a few of the built-in Script blocks to demonstrate how to create them from scratch.

noop

We'll start with creating the noop block (short for "no operation") which functions like a block comment by removing its inner contents from the rendered page:

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
  {{#noop}}
    <h2>Removed Content</h2>
    {{page}}
  {{/noop}}
  </div>
</div>

The noop block is also the smallest implementation possible which needs to inherit ScriptBlock class, overrides the Name getter with the name of the block and implements the WriteAsync() method which for the noop block just returns an empty Task there by not writing anything to the Output Stream, resulting in its inner contents being ignored:

public class NoopScriptBlock : ScriptBlock
{
    public override string Name => "noop";

    public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct)
        => Task.CompletedTask;
}

All Block's are executed with 3 parameters:

Registering Blocks

The same flexible registration options for Registering Script Methods is also available for registering blocks where if it wasn't already built-in, NoopScriptBlock could be registered by adding it to the ScriptBlocks collection:

var context = new ScriptContext {
    ScriptBlocks = { new NoopScriptBlock() },
}.Init();
Autowired using ScriptContext IOC

Autowired instances of script blocks and methods can also be created using ScriptContext's configured IOC where they're also injected with any registered IOC dependencies by registering them in the ScanTypes collection:

var context = new ScriptContext
{
    ScanTypes = { typeof(NoopScriptBlock) }
};
context.Container.AddSingleton<ICacheClient>(() => new MemoryCacheClient());
context.Init();

When the ScriptContext is initialized it will go through each Type and create an autowired instance of each Type and register them in the ScriptBlocks collection. An alternative to registering individual Types is to register an entire Assembly, e.g:

var context = new ScriptContext
{
    ScanAssemblies = { typeof(MyBlock).Assembly }
};

Where it automatically registers any Script Blocks or Methods contained in the Assembly where the MyBlock Type is defined.

bold

A step up from noop is the bold Script Block which markup its contents within the <b/> tag:

{{#bold}}This text will be bold{{/bold}}

Which calls the base.WriteBodyAsync() method to evaluate and write the Block's contents to the OutputStream using the current ScriptScopeContext:

public class BoldScriptBlock : ScriptBlock
{
    public override string Name => "bold";
    
    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        await scope.OutputStream.WriteAsync("<b>", token);
        await WriteBodyAsync(scope, block, token);
        await scope.OutputStream.WriteAsync("</b>", token);
    }
}

with

The with Block shows an example of utilizing arguments. To maximize flexibility arguments passed into your block are captured in a free-form string (specifically a ReadOnlyMemory<char>) which allows creating Blocks varying from simple arguments to complex LINQ-like expressions - a feature some built-in Blocks take advantage of.

The with block works similarly to handlebars with helper or JavaScript's with statement where it extracts the properties (or Keys) of an object and adds them to the current scope which avoids needing a prefix each property reference, e.g. being able to use {{Name}} instead of {{person.Name}}:

{{#with person}}
    Hi {{Name}}, your Age is {{Age}}.
{{/with}}

Also the with Block's contents are only evaluated if the argument expression is null.

The implementation below shows the optimal way to implement with by calling GetJsExpressionAndEvaluate() to resolve a cached AST token that's then evaluated to return the result of the Argument expression.

If the argument evaluates to an object it calls the ToObjectDictionary() extension method to convert it into a Dictionary<string,object> then creates a new scope with each property added as arguments and then evaluates the block's Body contents with the new scope:

public class WithScriptBlock : ScriptBlock
{
    public override string Name => "with";

    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var result = block.Argument.GetJsExpressionAndEvaluate(scope,
            ifNone: () => throw new NotSupportedException("'with' block does not have a valid expression"));

        if (result != null)
        {
            var resultAsMap = result.ToObjectDictionary();

            var withScope = scope.ScopeWithParams(resultAsMap);
             
            await WriteBodyAsync(withScope, block, token);
        }
    }
}

To better highlight how this works, a non-cached version of GetJsExpressionAndEvaluate() involves parsing the Argument string into an AST Token then evaluating it with the current scope:

block.Argument.ParseJsExpression(out token);
var result = token.Evaluate(scope);

The ParseJsExpression() extension method is able to parse virtually any JavaScript Expression into an AST tree which can then be evaluated by calling its token.Evaluate(scope) method.

Final implementation

The actual WithScriptBlock.cs used in #Script includes extended functionality which uses GetJsExpressionAndEvaluateAsync() to be able to evaluate both sync and async results.

else if/else statements

It also evaluates any block.ElseBlocks statements which is functionality available to all blocks which are able to evaluate any alternative else/else if statements when the main template isn't rendered, e.g. in this case when the with block is called with a null argument:

public class WithScriptBlock : ScriptBlock
{
    public override string Name => "with";

    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var result = await block.Argument.GetJsExpressionAndEvaluateAsync(scope,
            ifNone: () => throw new NotSupportedException("'with' block does not have a valid expression"));

        if (result != null)
        {
            var resultAsMap = result.ToObjectDictionary();

            var withScope = scope.ScopeWithParams(resultAsMap);
            
            await WriteBodyAsync(withScope, block, token);
        }
        else
        {
            await WriteElseAsync(scope, block.ElseBlocks, token);
        }
    }
}

This enables the with block to also evaluate async responses like the async results returned in async Database scripts, it's also able to evaluate custom else statements for rendering different results based on alternate conditions, e.g:

{{#with dbSingle("select * from Person where id = @id", { id }) }}
    Hi {{Name}}, your Age is {{Age}}.
{{else if id == 0}}
    id is required.
{{else}}
    No person with id {{id}} exists.
{{/with}}

if

Since all blocks are able to execute any number of {{else}} statements by calling base.WriteElseAsync(), the implementation for the #if block ends up being even simpler which just needs to evaluate the argument to bool.

If true it writes the body with WriteBodyAsync() otherwise it evaluates any else statements with WriteElseAsync():

/// <summary>
/// Handlebars.js like if block
/// Usages: {{#if a > b}} max {{a}} {{/if}}
///         {{#if a > b}} max {{a}} {{else}} max {{b}} {{/if}}
///         {{#if a > b}} max {{a}} {{else if b > c}} max {{b}} {{else}} max {{c}} {{/if}}
/// </summary>
public class IfScriptBlock : ScriptBlock
{
    public override string Name => "if";
    
    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope,
            ifNone: () => throw new NotSupportedException("'if' block does not have a valid expression"));

        if (result)
        {
            await WriteBodyAsync(scope, block, token);
        }
        else
        {
            await WriteElseAsync(scope, block.ElseBlocks, token);
        }
    }
}

while

Similar to #if, the #while block takes a boolean expression, except it keeps evaluating its body until the expression evaluates to false.

The implementation includes a safe-guard to ensure it doesn't exceed the configured ScriptContext.MaxQuota to avoid infinite recursion:

/// <summary>
/// while block
/// Usages: {{#while times > 0}} {{times}}. {{times - 1 |> to => times}} {{/while}}
///         {{#while b}} {{ false |> to => b }} {{else}} {{b}} was false {{/while}}
/// 
/// Max Iterations = Context.Args[ScriptConstants.MaxQuota]
/// </summary>
public class WhileScriptBlock : ScriptBlock
{
    public override string Name => "while";
    
    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct)
    {
        var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope,
            ifNone: () => throw new NotSupportedException("'while' block is not valid"));

        var iterations = 0;
        
        if (result)
        {
            do
            {
                await WriteBodyAsync(scope, block, ct);
                
                result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope,
                    ifNone: () => throw new NotSupportedException("'while' block is not valid"));

                Context.DefaultMethods.AssertWithinMaxQuota(iterations++);
                
            } while (result);
        }
        else
        {
            await WriteElseAsync(scope, block.ElseBlocks, ct);
        }
    }
}

each

From what we've seen up till now, the handlebars.js each block is also straightforward to implement which just iterates over a collection argument that evaluates its body with a new scope containing the elements properties, a conventional it binding for the element and an index argument that can be used to determine the index of each element:

/// <summary>
/// Handlebars.js like each block
/// Usages: {{#each customers}} {{Name}} {{/each}}
///         {{#each customers}} {{it.Name}} {{/each}}
///         {{#each customers}} Customer {{index + 1}}: {{Name}} {{/each}}
///         {{#each numbers}} {{it}} {{else}} no numbers {{/each}}
///         {{#each numbers}} {{it}} {{else if letters != null}} has letters {{else}} no numbers {{/each}}
/// </summary>
public class SimpleEachScriptBlock : ScriptBlock
{
    public override string Name => "each";

    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var collection = (IEnumerable) block.Argument.GetJsExpressionAndEvaluate(scope,
            ifNone: () => throw new NotSupportedException("'each' block does not have a valid expression"));

        var index = 0;
        if (collection != null)
        {
            foreach (var element in collection)
            {
                var scopeArgs = element.ToObjectDictionary();
                scopeArgs["it"] = element;
                scopeArgs[nameof(index)] = index++;
                
                var itemScope = scope.ScopeWithParams(scopeArgs);
                await WriteBodyAsync(itemScope, block, token);
            }
        }
        
        if (index == 0)
        {
            await WriteElseAsync(scope, block.ElseBlocks, token);
        }
    }
}

Despite its terse implementation, the above Script Block can be used to iterate over any expression that evaluates to a collection, inc. objects, POCOs, strings as well as Value Type collections like ints.

Built-in each

However the built-in EachScriptBlock.cs has a larger implementation to support its richer feature-set where it also includes support for async results, custom element bindings and LINQ-like syntax for maximum expressiveness whilst utilizing expression caching to ensure any complex argument expressions are only parsed once.

/// <summary>
/// Handlebars.js like each block
/// Usages: {{#each customers}} {{Name}} {{/each}}
///         {{#each customers}} {{it.Name}} {{/each}}
///         {{#each num in numbers}} {{num}} {{/each}}
///         {{#each num in [1,2,3]}} {{num}} {{/each}}
///         {{#each numbers}} {{it}} {{else}} no numbers {{/each}}
///         {{#each numbers}} {{it}} {{else if letters != null}} has letters {{else}} no numbers {{/each}}
///         {{#each n in numbers where n > 5}} {{it}} {{else}} no numbers > 5 {{/each}}
///         {{#each n in numbers where n > 5 orderby n skip 1 take 2}} {{it}} {{else}}no numbers > 5{{/each}}
/// </summary>
public class EachScriptBlock : ScriptBlock { ... }

By using ParseJsExpression() to parse expressions after each "LINQ modifier", each supports evaluating complex JavaScript expressions in each of its LINQ querying features, e.g:

Custom bindings

When using a custom binding like {{#each c in customers}} above, the element is only accessible with the custom c binding which is more efficient when only needing to reference a subset of the element's properties as it avoids adding each of the elements properties in the items execution scope.

Check out LINQ Examples for more live previews showcasing advanced usages of the {{#each}} block.

raw

The {{#raw}} block is similar to handlebars.js's raw-helper which captures the template's raw text content instead of having its content evaluated, making it ideal for emitting content that could contain template expressions like client-side JavaScript or template expressions that shouldn't be evaluated on the server such as Vue, Angular or Ember templates:

{{#raw}}
<div id="app">
    {{ message }}
</div>
{{/raw}}

When called with no arguments it will render its unprocessed raw text contents. When called with a single argument, e.g. {{#raw varname}} it will instead save the raw text contents to the specified global PageResult variable and lastly when called with the appendTo modifier it will append its contents to the existing variable, or initialize it if it doesn't exist.

This is now the preferred approach used in all .NET Core and .NET Framework Web Templates for pages and partials to append any custom JavaScript script blocks they need on the page, e.g:

{{#raw appendTo scripts}}
<script>
    //...
</script>
{{/raw}}

Where any captured custom scripts are rendered at the bottom of _layout.html with:

    <script src="/assets/js/default.js"></script>

    {{ scripts |> raw }}

</body>
</html>

The implementation to support each of these usages is contained within RawScriptBlock.cs below which inspects the block.Argument to determine whether it should capture the contents into the specified variable or write its raw string contents directly to the OutputStream:

/// <summary>
/// Special block which captures the raw body as a string fragment
///
/// Usages: {{#raw}}emit {{ verbatim }} body{{/raw}}
///         {{#raw varname}}assigned to varname{{/raw}}
///         {{#raw appendTo varname}}appended to varname{{/raw}}
/// </summary>
public class RawScriptBlock : ScriptBlock
{
    public override string Name => "raw";
    
    public override ScriptLanguage Body => ScriptVerbatim.Language;

    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var strFragment = (PageStringFragment)block.Body[0];

        if (!block.Argument.IsNullOrWhiteSpace())
        {
            Capture(scope, block, strFragment);
        }
        else
        {
            await scope.OutputStream.WriteAsync(strFragment.Value.Span, token);
        }
    }

    private static void Capture(
        ScriptScopeContext scope, PageBlockFragment block, PageStringFragment strFragment)
    {
        var literal = block.Argument.Span.AdvancePastWhitespace();
        bool appendTo = false;
        if (literal.StartsWith("appendTo "))
        {
            appendTo = true;
            literal = literal.Advance("appendTo ".Length);
        }

        literal = literal.ParseVarName(out var name);
        var nameString = name.Value();
        if (appendTo && scope.PageResult.Args.TryGetValue(nameString, out var oVar)
                        && oVar is string existingString)
        {
            scope.PageResult.Args[nameString] = existingString + strFragment.Value;
            return;
        }

        scope.PageResult.Args[nameString] = strFragment.Value.ToString();
    }
}

function

The {{#function}} block lets you define reusable functions in your page. Unlike other Script Blocks, the body of a function block is parsed as a code block where only the return value is used. Any output generated from executing the function is discarded. Use the partial block if you instead want to define reusable fragments.

In its simplest form, defining a function requires the function name and a body that returns a value with the return method, e.g:

{{#function hi}}
    'hello' |> return
{{/function}}

This creates a compiled delegate and assigns it to the pages scope where it can be invoked as a normal function, e.g:

hi()

Functions can specify arguments using a JavaScript arguments list:

{{#function calc(a, b) }}
    a * b |> to => c
    return(a + b + c)
{{/function}}

Where it can be called as normal function or as an extension method:

calc(1,2)
1.calc(2)

Functions can also be called recursively:

{{#function fib(num) }}
    #if num <= 1
        return(num)
    /if
    return (fib(num-1) + fib(num-2))
{{/function}}

Although their limited to the configured MaxStackDepth.

Source code for FunctionScriptBlock.cs.

defn

Similar to {{#function}} above, the {{#defn}} script block lets you define a function using lisp. The resulting function is exported as a C# delegate where it can be invoked like any other Script method.

An equivalent calc and fib function in lisp looks like:

{{#defn calc [a, b] }}
    (def c (* a b))
    (+ a b c)
{{/defn}}

calc: {{ calc(1,2) }}

{{#defn fib [n] }}
    (if (<= n 1)
        n
        (+ (fib (- n 1))
           (fib (- n 2)) ))
{{/defn}}

fib: {{ 10.fib() }} 

As in most Lisp expressions, the last expression executed is the implicit return value.

The defn Script Block is automatically registered when the Lisp language is registered.

capture

The {{#capture}} block is similar to the raw block except instead of using its raw text contents, it instead evaluates its contents and captures the output. It also supports evaluating the contents with scoped arguments where by each property in the object dictionary is added in the scoped arguments that the block is executed with:

/// <summary>
/// Captures the output and assigns it to the specified variable.
/// Accepts an optional Object Dictionary as scope arguments when evaluating body.
///
/// Usages: {{#capture output}} {{#each args}} - [{{it}}](/path?arg={{it}}) {{/each}} {{/capture}}
///         {{#capture output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}}
///         {{#capture appendTo output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}}
/// </summary>
public class CaptureScriptBlock : ScriptBlock
{
    public override string Name => "capture";

    public override ScriptLanguage Body => ScriptTemplate.Language;

    internal struct Tuple
    {
        internal string name;
        internal Dictionary<string, object> scopeArgs;
        internal bool appendTo;
        internal Tuple(string name, Dictionary<string, object> scopeArgs, bool appendTo)
        {
            this.name = name;
            this.scopeArgs = scopeArgs;
            this.appendTo = appendTo;
        }
    }

    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var tuple = Parse(scope, block);
        var name = tuple.name;

        using (var ms = MemoryStreamFactory.GetStream())
        {
            var useScope = scope.ScopeWith(tuple.scopeArgs, ms);

            await WriteBodyAsync(useScope, block, token);

            var capturedOutput = ms.ReadToEnd();

            if (tuple.appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar)
                         && oVar is string existingString)
            {
                scope.PageResult.Args[name] = existingString + capturedOutput;
                return;
            }
        
            scope.PageResult.Args[name] = capturedOutput;
        }
    }

    //Extract usages of Span outside of async method 
    private Tuple Parse(ScriptScopeContext scope, PageBlockFragment block)
    {
        if (block.Argument.IsNullOrWhiteSpace())
            throw new NotSupportedException("'capture' block is missing variable name to assign output to");
        
        var literal = block.Argument.AdvancePastWhitespace();
        bool appendTo = false;
        if (literal.StartsWith("appendTo "))
        {
            appendTo = true;
            literal = literal.Advance("appendTo ".Length);
        }
            
        literal = literal.ParseVarName(out var name);
        if (name.IsNullOrEmpty())
            throw new NotSupportedException("'capture' block is missing variable name to assign output to");

        literal = literal.AdvancePastWhitespace();

        var argValue = literal.GetJsExpressionAndEvaluate(scope);

        var scopeArgs = argValue as Dictionary<string, object>;

        if (argValue != null && scopeArgs == null)
            throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary");

        return new Tuple(name.ToString(), scopeArgs, appendTo);
    }
}

With this we can dynamically generate some markdown, capture its contents and convert the resulting markdown to html using the markdown Filter transformer:

{{#capture todoMarkdown { items:[1,2,3] } }}
## TODO List
{{#each items}}
  - Item {{it}}
{{/each}}
{{/capture}}

{{todoMarkdown |> markdown}}

markdown

The {{#markdown}} block makes it even easier to embed markdown content directly in web pages which works as you'd expect where content in a markdown block is converted into HTML, e.g:

{{#markdown}}
## TODO List
  - Item 1
  - Item 2
  - Item 3
{{/markdown}}

Which is now the easiest and preferred way to embed Markdown content in content-rich hybrid web pages like Razor Rockstars content pages, or even this blocks.html WebPage itself which makes extensive use of markdown.

As markdown block only supports 2 usages its implementation is much simpler than the capture block above:

/// <summary>
/// Converts markdown contents to HTML using the configured MarkdownConfig.Transformer.
/// If a variable name is specified the HTML output is captured and saved instead. 
///
/// Usages: {{#markdown}} ## The Heading {{/markdown}}
///         {{#markdown content}} ## The Heading {{/markdown}} HTML: {{content}}
/// </summary>
public class MarkdownScriptBlock : ScriptBlock
{
    public override string Name => "markdown";
    
    public override async Task WriteAsync(
        ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
    {
        var strFragment = (PageStringFragment)block.Body[0];

        if (!block.Argument.IsNullOrWhiteSpace())
        {
            Capture(scope, block, strFragment);
        }
        else
        {
            await scope.OutputStream.WriteAsync(MarkdownConfig.Transform(strFragment.ValueString), token);
        }
    }

    private static void Capture(
        ScriptScopeContext scope, PageBlockFragment block, PageStringFragment strFragment)
    {
        var literal = block.Argument.AdvancePastWhitespace();

        literal = literal.ParseVarName(out var name);
        var nameString = name.ToString();
        scope.PageResult.Args[nameString] = MarkdownConfig.Transform(strFragment.ValueString).ToRawString();
    }
}

Use Alternative Markdown Implementation

By default ServiceStack uses an interned implementation of MarkdownDeep for rendering markdown, you can get ServiceStack to use an alternate Markdown implementation by overriding MarkdownConfig.Transformer.

E.g. to use the richer Markdig implementation, install the Markdig NuGet package:

PM> Install-Package Markdig

Then assign a custom IMarkdownTransformer:

public class MarkdigTransformer : IMarkdownTransformer
{
    private Markdig.MarkdownPipeline Pipeline { get; } = 
        Markdig.MarkdownExtensions.UseAdvancedExtensions(new Markdig.MarkdownPipelineBuilder()).Build();

    public string Transform(string markdown) => Markdig.Markdown.ToHtml(markdown, Pipeline);
}

MarkdownConfig.Transformer = new MarkdigTransformer();

keyvalues

The {{#keyvalues}} block lets you define a key value dictionary in free-text which is useful in Live Documents for capturing a data structure like expenses in free-text, e.g:

{{#keyvalues monthlyExpenses}}
Rent            1000
Internet        50
Mobile          50
Food            400
Misc            200
{{/keyvalues}}
{{ monthlyExpenses |> values |> sum |> to => totalExpenses }}

By default it's delimited by the first space ' ', but if the first key column can contain spaces you can specify to use a different delimiter, e.g:

{{#keyvalues monthlyRevenues ':'}}
Salary:         4000
App Royalties:  200
{{/keyvalues}}

The KeyValuesScriptBlock.cs implementation is fairly straight forward where it passes the string body to ParseKeyValueText() method with an optional delimiter and assigns the results to the specified variable name:

public class KeyValuesScriptBlock : ScriptBlock
{
    public override string Name => "keyvalues";
    
    public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct)
    {
        var literal = block.Argument.Span.ParseVarName(out var name);

        var delimiter = " ";
        literal = literal.AdvancePastWhitespace();
        if (literal.Length > 0)
        {
            literal = literal.ParseJsToken(out var token);
            if (!(token is JsLiteral litToken))
              throw new NotSupportedException($"#keyvalues expected delimiter but was {token.DebugToken()}");
            delimiter = litToken.Value.ToString();
        }
        
        var strFragment = (PageStringFragment)block.Body[0];
        var strDict = strFragment.ValueString.Trim().ParseAsKeyValues(delimiter);
        scope.PageResult.Args[name.ToString()] = strDict;

        return TypeConstants.EmptyTask;
    }
}

csv

Similar to keyvalues, you can specify a multi-column inline data set using the {{#csv}} block, e.g:

The CsvScriptBlock.cs implementation is similar to keyvalues except passes the trimmed string body to FromCsv into a string List and assigns the result to the specified name:

public class CsvScriptBlock : ScriptBlock
{
    public override string Name => "csv";
    
    public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct)
    {
        var literal = block.Argument.ParseVarName(out var name);
        
        var strFragment = (PageStringFragment)block.Body[0];
        var trimmedBody = StringBuilderCache.Allocate();
        foreach (var line in strFragment.ValueString.ReadLines())
        {
            trimmedBody.AppendLine(line.Trim());
        }
        var strList = trimmedBody.ToString().FromCsv<List<List<string>>>();
        scope.PageResult.Args[name.ToString()] = strList;

        return TypeConstants.EmptyTask;
    }
}

partial

The {{#partial}} block lets you create In Memory partials which is useful when working with partial filters like selectPartial as it lets you declare multiple partials within the same page, instead of requiring multiple individual files. See docs on Inline partials for a Live comparison of using in memory partials.

html

The purpose of the html blocks is to pack a suite of generically useful functionality commonly used when generating html. All html blocks inherit the same functionality with blocks registered for the most popular HTML elements, currently:

script, style, link, meta, ul, ol, li, div, p, form, input, select, option, textarea, button, table, tr, td, thead, tbody, tfoot, dl, dt, dd, span, a, img, em, b, i, strong.

Ultimately they reduce boilerplate, e.g. you can generate a menu list with a single block:

{{#ul {each:items, id:'menu', class:'nav'} }} 
    <li>{{it}}</li> 
{{/ul}}

A more advanced example showcasing many of its different features is contained in the example below:

{{#ul {if:hasAccess, each:items, where:'Age > 27', 
        class:['nav', !disclaimerAccepted ? 'blur' : ''], id:`menu-${id}`, selected:true} }}
    {{#li {class: {alt:isOdd(index), active:Name==highlight} }} {{Name}} {{/li}}
{{else}}
    <div>no items</div>
{{/ul}}

This example utilizes many of the features in html blocks, namely:

All other properties like id and selected are treated like HTML attributes where if the property is a boolean like selected it's only displayed if its true otherwise all other html attribute's names and values are emitted as normal.

For a better illustration we can implement the same functionality above without using any html blocks:

{{#if hasAccess}}
    {{ items |> where => it.Age > 27 |> to => items }}
    {{#if !isEmpty(items)}}
        <ul {{ ['nav', !disclaimerAccepted ? 'blur' : ''] |> htmlClass }} id="menu-{{id}}">
        {{#each items}}
            <li {{ {alt:isOdd(index), active:Name==highlight} |> htmlClass }}>{{Name}}</li>
        {{/each}}
        </ul>
    {{else}}
        <div>no items</div>
    {{/if}}
{{/if}}

The same functionality using C# Razor with the latest C# language features enabled can be implemented with:

@{
    var persons = (items as IEnumerable<Person>)?.Where(x => x.Age > 27);
}
@if (hasAccess)
{
    if (persons?.Any() == true)
    {
        <ul id="menu-@id" class="nav @(!disclaimerAccepted ? "hide" : "")">
            @{
                var index = 0;
            }
            @foreach (var person in persons)
            {
                <li class="@(index++ % 2 == 1 ? "alt " : "" )@(person.Name == activeName ? "active" : "")">
                    @person.Name
                </li>
            }
        </ul>
    }
    else
    {
        <div>no items</div>
    }
}

ServiceStack Blocks

ServiceStack's Blocks are registered by default in #Script Pages that can be registered in a new ScriptContext by adding the ServiceStackScriptBlocks plugin:

var context = new ScriptContext {
    Plugins = {
        new ServiceStackScriptBlocks(),
    }
}.Init();

Mix in NUglify

You can configure ServiceStack and #Script to use Nuglify's Advanced HTML, CSS, JS Minifiers using mix with:

$ mix nuglify 

Which will add Configure.Nuglify.cs to your HOST project.

To assist with debugging during development, no minification is applied when DebugMode=true.

All minifier Blocks supports an additional <name> argument to store the captured output of the minifier block into, e.g:

{{#minifier capturedMinification}} ... {{/minifier}}
{{capturedMinification}}

That also supports using the appendTo modifier to concatenate the minified output instead of replacing it, e.g:

{{#minifier appendTo capturedMinification}} ... {{/minifier}}
{{#minifier appendTo capturedMinification}} ... {{/minifier}}
{{capturedMinification}}

minifyjs

Use the minifyjs block to minify inline JavaScript:

minifycss

Use the minifycss block to minify inline CSS:

minifyhtml

Use the minifyhtml block to minify HTML:

svg

Use the svg block in your _init.html Startup Script to register SVG Images with ServiceStack.

Removing Blocks

Like everything else in #Script, all built-in Blocks can be removed. To make it easy to remove groups of related blocks you can just remove the plugin that registered them using the RemovePlugins() API, e.g:

var context = new ScriptContext()
    .RemovePlugins(x => x is DefaulScripttBlocks) // Remove default blocks
    .RemovePlugins(x => x is HtmlScriptBlocks)    // Remove all html blocks
    .Init();

Or you can use the OnAfterPlugins callback to remove any individual blocks or filters that were added by any plugin.

E.g. the capture block can be removed with:

var context = new ScriptContext {
        OnAfterPlugins = ctx => ctx.RemoveBlocks(x => x.Name == "capture")
    }
    .Init();

made with by ServiceStack