Introduction

ServiceStack Templates is a simple and elegant, highly-extensible, sandboxed, high-performance general-purpose templating engine for .NET 4.5 and .NET Core. It's designed from the ground-up to be incrementally adoptable where its basic usage is simple enough for non-technical users to use whilst it progressively enables access to more power and functionality allowing it to scale up to support full server-rendering Web Server workloads and beyond. Its high-fidelity with JavaScript syntax allows it to use a common language for seamlessly integrating with client-side JavaScript Single Page App frameworks where its syntax is designed to be compatible with Vue filters.

Instant Startup

There's no pre-compilation, pre-loading or Startup penalty, all Pages are lazily loaded on first use and cached for fast subsequent evaluation. Its instant Startup, fast runtime performance and sandboxed isolation opens it up to a myriad of new use-cases which can enhance .NET Apps with a rich Live programming experience.

Fast Runtime Performance

ServiceStack Templates is fast, parsing is done using StringSegment for minimal GC pressure, all I/O is non-blocking inc. async writes to OutputStream's. There's no buffering: Layouts, Pages and Partials are asynchronously written to a forward only stream. There's no runtime reflection, each filter or binding within template expressions executes compiled and cached C# Expressions.

Pure, Functional and Reactive

Templates are pure at both the library-level where they're a clean library with no external dependencies outside ServiceStack.Common, no coupling to web frameworks, external configuration files, designer tooling, build tools, pre-compilation steps or require any special deployment requirements. It binds to simple, clean, small interfaces for its Virtual File System, IOC and AppSettings providers which are easily overridden to integrate cleanly into external web frameworks.

They're also pure at the code-level where it doesn't have any coupling to concrete dependencies, components or static classes. Default filters don't mutate any external state. There's no imperative statements, everything is an expression forcing a more readable and declarative programming-style that's easier to quickly determine the subject of expressions and the states that need to be met for filters to be executed. Conceptually Templates are "evaluated" in that they take in arguments, filters and templates as inputs and evaluates them to an output stream. They're highly testable by design where the same environment used to create the context can easily be re-created in Unit tests, including simulating pages in a File System using its In Memory Virtual File System.

Optimal Development Experience

The above attributes enables an instant iterative development workflow with a Live Development experience that supports configuration-free Hot Reloading out of the box that lets you build entire Wep Apps and Web APIs without ever having to compile or manually Refresh pages.

Simplicity end-to-end

There are 2 main concepts in Template Expressions: Arguments - variables which can be made available through a number of cascading sources and Filters - public C# methods that exist in the list of TemplateFilters registered in the PageResult or TemplateContext that templates are executed within.

Layouts, Pages and Partials are all just "pages", evaluated in the same way with access to arguments and filters. Parameters passed to partials are just scoped arguments, accessed like any other arguments. Typically pages are sourced from the configured File System but when access to more advanced functionality is required they can also be Code Pages implemented in pure C# that are otherwise interchangeable and can be used anywhere a normal page is requested.

There's no language constructs or reserved words in Templates itself, all functionality is implemented inside filters. There's also nothing special about the Default Filters included with Templates other than they're pre-registered by default. External filters can do anything built-in filters can do which can just as easily be shadowed or removed giving you a clean slate where you're free to define your own language and preferred language naming scheme.

They're non-invasive, by default ServiceStack's View Engine is configured to handle .html files but can be configured to handle any html extension or text file format. When a page doesn't contain any Template Expressions it's contents are returned as-is, making it easy to adopt in existing static websites.

Templates are sandboxed, they can't call static or instance methods, invoke setters or access anything outside of the arguments and filters made available to them. Without filters, expressions wouldn't have any methods they can call, leaving them with the only thing they can do which is access arguments and replace their variable placeholders, including the {{ page }} placeholder to tell the Layout where to render the page:

Quick Example Walkthrough

_layout.html
page.html

To render the pages we first create and initialize a TemplateContext

var context = new TemplateContext().Init();

The TemplateContext is the sandbox where all templates are executed within, everything your templates have access to and generates is maintained within the TemplateContext. Once initialize you can start using it to evaluate templates which you can do with just:

var output = context.EvaluateTemplate("{{ 12.34 | currency }}");

Templates only have access to filters and arguments defined within its Context, which for an empty Context are the comprehensive suite of safe Default Filters and HTML Filters.

Typically you'll want to use ServiceStack templates to render entire pages which are sourced from its configured Virtual File System which uses an In Memory Virtual File System by default that we can programmatically populate:

context.VirtualFiles.Write("_layout.html", "I am the Layout: <b>{{ page }}</b>");
context.VirtualFiles.Write("page.html", "I am the Page");

Templates are rendered using a PageResult essentially a rendering context that needs to be provided the Page to render:

var pageResult = new PageResult(context.GetPage("page"));

The template output can then be asynchronously rendered to any Stream:

await pageResult.WriteToAsync(responseStream);

Or to access the output as a string you can use the convenience extension method:

string output = await pageResult.RenderToStringAsync();

All I/O within Templates is non-blocking, but if you're evaluating an adhoc template or using the default In Memory Virtual FileSystem there's no I/O so you can safely block to get the generated output with:

string output = pageResult.Result;

Both APIs returns the result you see in the Live Example above.

Cascading Resolution

There's no forced special centralized folders like /Views or /Views/Shared required to store layouts or share partials or artificial "Areas" concept to isolate website sections. Different websites or sections are intuitively grouped into different folders and Templates automatically resolves the closest layout it finds for each page. Cascading resolution also applies to including files or partial pages where you can use just its name to resolve the closest one, or an absolute path from the WebRootPath to include a specific partial or file from a different folder.

High-level, Declarative and Intent-based

High-level APIs are usually synonymous with being slow by virtue of paying a penalty for their high-level abstraction, but in the domain of I/O and Streams such-as rendering text to Streams they make it trivial to compose high-level functionality that's implemented more efficiently than would be typically written in C# / ASP.NET MVC Apps.

As an example let's analyze the Template below:

{{ 'select url from quote where id= @id' | dbScalar({ id }) | urlContents | markdown | assignTo: quote }}

{{ quote | replace('Razor', 'Templates') | replace('2010', now.Year) | raw }}

The intent of Template code should be clear even if it's the first time reading it. From left-to-right we can deduce that it retrieves a url from the quote table, downloads its contents of and converts it to markdown before replacing the text "Razor" and "2010" and displaying the raw non-HTML encoded html output.

Implementation using ASP.NET MVC

In MVC the typical and easiest approach would be to create a an MVC Controller Action, use EF to make a sync call to access the database, a sync call with a new HTTP Client to download the content which is buffered inside the Controller action before returned inside a View Model that is handed off to MVC to execute the View inside the default Layout.

Implementation using Templates

What does Templates do? lets step through the first filter:

What filter implementation gets called depends on which DB Filter is registered, if your RDBMS supports ADO.NET's async API you can register TemplateDbFiltersAsync to execute all queries asynchronously, otherwise if using an RDBMS whose ADO.NET Provider doesn't support async you can register the TemplateDbFilters to execute each DB request synchronously without paying for any pseudo-async overhead, in each case the exact same code executes the most optimal ADO.NET APIs. Template also benefits from using the much faster OrmLite and also saves on the abstraction cost from generating a parameterized SQL Statement from a Typed Expression-based API.

Arguments chain

The next question becomes what is id bound to? Similar to JavaScript's prototype chain it resolves the closest argument in its Arguments hierarchy, e.g. when evaluated as a view page it could be set by the queryString or when the same page is evaluated as a partial it could be set from the scoped arguments it was called with.

Async I/O

The url returned from the db is then passed to the urlContents filter which if it was the last filter in the expression avoids any buffering by asynchronously streaming the url content stream directly to the forward-only HTTP Response Stream:

{{ url | urlContents }}

urlContents is a Block Filter which instead of returning a value writes its response to the OutputStream it's given. But then how could we convert it to Markdown if it's already written to the Response Stream? Templates analyzes the Expression's AST to determine if there's any filters remaining, if there is it gives the urlContents Block filter a MemoryStream to write to, then forwards its buffered output to the next filter. Since they don't return values, the only thing that can come after a Block Filter are other Block filters or Stream Transformers. markdown is one such Filter Transformer which takes in a stream of Markdown and returns a Stream of HTML.

{{ url | urlContents | markdown }}
Same intent, different implementations

The assignTo filter is used to set a value in the current scope. After Block Filters, a different assignTo Block Filter is used with the same name and purpose but a different implementation that reads all the contents of the stream into a UTF-8 string and sets a value in the current scope before returning an empty Stream so nothing is written to the Response.

{{ url | urlContents | markdown | assignTo: quote }}

Once the streamed output is captured and assigned it goes back into becoming a normal argument that opens it up to be able to use all filters again, which is how we're able to use the string replace filters before rendering the final result :)

/examples/qotd?id=1

Using the most efficient implementation allowable

So whilst it conceptually looks like each filter is transforming large buffered strings inside every filter, the expression is inspected to utilize the most efficient implementation allowable. At the same time filters are not statically bound to any implementation so you could for instance insert a Custom Filter before the Default Filters containing the same name and arguments count to have templates execute your custom filters instead, all whilst the template's source code and intent remains untouched.

Intent-based code is easier to augment

If it was later discovered that some URLs were slow or rate-limited and you needed to introduce caching, your original C# code would require a fair amount of rework, in templates you can simply add WithCache to call the urlContentsWithCache filter to return locally cached contents on subsequent requests.

{{ url | urlContentsWithCache | markdown }}

Simplified Language

As there's very little ceremony in Templates, a chain of filters looks like it's using its own DSL to accomplish each task and given implementing and registering custom filters is trivial you're encouraged to write the intent of your code first then implement any filters that are missing to realize its intent. Once you've captured the intent of what you want to do, it's less likely to ever need to change, focus is instead on fixing any bugs and making the filter implementations as efficient as possible, which benefits all existing code using the same filter.

To improve readability and make it more approachable, templates aims to normalize the mechanics of the underlying implementation from the code's intent so you can use the same syntax to access an argument, e.g. {{ arg }} as you would a filter without arguments {{ now }} and just like JavaScript you can use obj.Property syntax to access both a public property on a Type or an entry in a Dictionary.

Late-bound flexibility

There's no static coupling to concrete classes, static methods or other filters, ambiguous method exceptions or namespace collisions. Each filter is self-contained and can easily be shared and dropped into any Web App by registering them in a list. Inspired by the power and flexibility in Smalltalk and LISP, filters are late-bound at run-time to the first matching filter in the user-defined list of TemplateFilters. This ability to shadow filters enables high-level intent-based APIs decoupled from implementations which sendToAutoQuery leverages to automatically route filter invocations to the appropriate implementation depending of if it's an AutoQuery RDBMS or an AutoQuery Data request, masking their implementations as a transparent detail. This flexibility also makes it easy create proxies, intercept and inject behavior like logging or profiling without modifying existing filter implementations or template source code.

made with by ServiceStack