View Engine

One of the most popular use-cases for a high-performance Templating engine like ServiceStack Templates is as a server-side HTML View Engine for .NET Web Applications where it can provide a simpler, cleaner and portable alternative than Razor and Razor Pages in ASP.NET and ASP.NET Core Web Apps.

View Engine in ServiceStack

The TemplatePagesFeature plugin provides a first-class experience for generating dynamic websites where it's able to generate complete server-generated websites (like this one) without requiring any additional Controllers or Services.

To enable Template Pages as a View Engine in ServiceStack you just need to register the TemplatePagesFeature plugin:

public void Configure(Container container)
{
    Plugins.Add(new TemplatePagesFeature());
}

TemplatePagesFeature is a subclass of TemplateContext which defines the context on which all ServiceStack Template Pages are executed within. It provides deep integration within ServiceStack by replacing the TemplateContext's stand-alone dependencies with ServiceStack AppHost providers, where it:

If preferred, you can change which .html extension gets handled by Template Pages with:

Plugins.Add(new TemplatePagesFeature { HtmlExtension = "htm" });

Runs Everywhere

The beauty of ServiceStack Templates working natively with ServiceStack is that it runs everywhere ServiceStack does which is in all major .NET Server Platforms. That is, your same Templates-based Web Application is able to use the same Templates implementation, "flavour" and feature-set and is portable across whichever platform you choose to host it on:

Once registered, TemplatePagesFeature gives all your .html pages Template super powers where sections can be compartmentalized and any duplicated content can now be extracted into reusable partials, metadata can be added to the top of each page and its page navigation dynamically generated, contents of files and urls can be embedded directly and otherwise static pages can come alive with access to Default Filters.

Page Based Routing

Any .html page available from your AppHost's configured Virtual File Sources can be called directly, typically this would mean the File System which in a .NET Core Web App starts from the WebRootPath (usually /wwwroot) so a request to /docs/view-engine goes through all configured VirtualFileSources to find the first match, which for this website is the file /src/wwwroot/docs/view-engine.html.

Pretty URLs by default

Essentially Template Pages embraces conventional page-based routing which enables pretty urls inferred from the pages file and directory names where each page can be requested with or without its .html extension:

path page
/db  
/db.html /db.html
/posts/new  
/posts/new.html /posts/new.html

The default route / maps to the index.html in the directory if it exists, e.g:

path page
/ /index.html
/index.html /index.html

Dynamic Page Routes

In addition to these static conventions, Template Pages now supports Nuxt-like Dynamic Routes where any file or directory names prefixed with an _underscore enables a wildcard path which assigns the matching path component to the arguments name:

path page arguments
/ServiceStack /_user/index.html user=ServiceStack
/posts/markdown-example /posts/_slug/index.html slug=markdown-example
/posts/markdown-example/edit /posts/_slug/edit.html slug=markdown-example

Layout and partial recommended naming conventions

The _underscore prefix for declaring wildcard pages is also what is used to declare "hidden" pages, to distinguish them from hidden partials and layouts, the recommendation is for them to include layout and partial their name, e,g:

Pages with layout or partial in their name remain hidden and are ignored in wildcard path resolution.

If following the recommended _{name}-partial.html naming convention, you'll be able to reference them using just their name:

{{ 'menu' | partial }}          // Equivalent to:
{{ '_menu-partial' | partial }}

View Pages

View Pages lets you use .html Template Pages to render the HTML for Services Responses. It works similarly to Razor ViewPages where it uses first matching View Page with the Response DTO is injected as the `Model` property. The View Pages can be in any folder within the /Views folder using the format {PageName}.html where PageName can be either the Request DTO or Response DTO Name, but all page names within the /Views folder need to be unique.

Just like ServiceStack.Razor, you can specify to use different Views or Layouts by returning a custom HttpResult, e.g:

public object Any(MyRequest request)
{
    ...
    return new HttpResult(response)
    {
        View = "CustomPage",
        Template = "_custom-layout",
    };
}

Or add the [ClientCanSwapTemplates] Request Filter attribute to allow clients to specify which View and Template to use via the query string, e.g: ?View=CustomPage&Template=_custom-layout

Additional examples of dynamically specifying the View and Template are available in TemplateViewPagesTests.

Cascading Layouts

One difference from Razor is that it uses a cascading _layout.html instead of /Views/Shared/_Layout.cshtml.

So if your view page was in:

/Views/dir/MyRequest.html

It will use the closest `_layout.html` it can find starting from:

/Views/dir/_layout.html
/Views/_layout.html
/_layout.html

Layout Selection

Unless it's a complete HTML Page (e.g. starts with html or HTML5 tag) the page gets rendered using the closest _layout.html page it can find starting from the directory where the page is located, traversing all the way up until it reaches the root directory. Which for this page uses the /src/wwwroot/_layout.html template in the WebRoot directory, which as it's in the root directory, is the fallback Layout for all .html pages.

Pages can change the layout they use by either adding their own _layout.html page in their sub directory or specifying a different layout in their pages metadata header, e.g:

<!--
layout: mobile-layout
-->

Where it instead embed the page using the closest mobile-layout.html it can find, starting from the Page's directory. If your templates are instead embedded in the different folder you can request it directly from the root dir:

<!--
layout: templates/mobile-layout
-->

Request Variables

The QueryString and FORM variables sent to the page are available as arguments within the page, so a request like /docs/view-engine?id=1 can access the id param with {{ id }}. To help with generating navigation, the following Request Variables are also available:

You can use {{ PathInfo }} to easily highlight the active link in a links menu as done in sidebar.html:

<nav id="sidebar">
<div class="inner">

<ul>
    <li>
        <h5>docs</h5>
        <ul>
            {{#each docLinks}}
            <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li>
            {{/each}}
        </ul>
    </li>

    <li>
        <h5>use cases</h5>
        <ul>
            {{#each useCaseLinks}}
            <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li>
            {{/each}}
        </ul>
    </li>

    <li>
        <h5>linq examples</h5>
        <ul>
            {{#each linqLinks}}
            <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li>
            {{/each}}
        </ul>
    </li>
</ul>

</div>
</nav>

Init Pages

Just as how Global.asax.cs can be used to run Startup initialization logic in ASP.NET Web Applications and Startup.cs in .NET Core Apps, you can now add a /_init.html page for Templates logic that's only executed once on Startup.

This is used in the Blog Web App's _init.html where it will create a new blog.sqlite database if it doesn't exist seeded with the UserInfo and Posts Tables and initial data, e.g:

{{  `CREATE TABLE IF NOT EXISTS "UserInfo" 
    (
        "UserName" VARCHAR(8000) PRIMARY KEY, 
        "DisplayName" VARCHAR(8000) NULL, 
        "AvatarUrl" VARCHAR(8000) NULL, 
        "AvatarUrlLarge" VARCHAR(8000) NULL, 
        "Created" VARCHAR(8000) NOT NULL,
        "Modified" VARCHAR(8000) NOT NULL
    );`    
    | dbExec
}}

{{ dbScalar(`SELECT COUNT(*) FROM Post`) | assignTo: postsCount }}

{{#if postsCount == 0 }}

    ========================================
    Seed with initial UserInfo and Post data
    ========================================

    ...

{{/if}

{{ htmlError }}

The output of the _init page is captured in the initout argument which can be later inspected as a normal template argument as seen in log.html:

<div>
    Output from init.html:

    <pre>{{initout | raw}}</pre>
</div>

If there was an Exception with any of the SQL Statements it will be displayed in the {{ htmlError }} filter which can be later viewed in the /log page above.

Ignoring Pages

You can ignore ServiceStack Templates from evaluating static .html files with the following page arguments:

<!--
ignore: page
-->

Nothing in this page will be evaluated but will still be rendered inside the closest Layout.
<!--
ignore: template
-->

This page will not be evaluated nor will it have a Layout.
<!--
layout: none
-->

This page will be evaluated but rendered without a layout.
Complete .html pages starting with <!DOCTYPE HTML> or <html have their layouts ignored by default.

Templates Admin Service

The new Templates Admin Service lets you run admin actions against a running instance which by default is only accessible to Admin users and can be called with:

/templates/admin

Which will display the available actions which are currently only:

Zero downtime deployments

These actions are useful after an xcopy/rsync deployment to enable zero downtime deployments by getting a running instance to invalidate all internal caches and force existing pages to check if it has been modified, the next time their called.

Actions can be invoked in the format with:

/templates/admin/{Actions}

Which can be used to call 1 or more actions:

/templates/admin/invalidateAllCaches
/templates/admin/invalidateAllCaches,RunInitPage

By default it's only available to be called by **Admin** Users (or AuthSecret) but can be changed with:

Plugins.Add(new TemplatePagesFeature {
    TemplatesAdminRole = RoleNames.AllowAnyUser, // Allow any Authenticated User to call /templates/admin
    //TemplatesAdminRole = RoleNames.AllowAnon,  // Allow anyone
    //TemplatesAdminRole = null,                 // Do not register /templates/admin service
});

This also the preferred way to enable the Metadata Debug Template in production, which is only available in DebugMode and Admin Role by default:

Plugins.Add(new TemplatePagesFeature {
    MetadataDebugAdminRole = RoleNames.Admin,          // Only allow Admin users to call /metadata/debug
    //MetadataDebugAdminRole = RoleNames.AllowAnyUser, // Allow Authenticated Users
    //MetadataDebugAdminRole = RoleNames.AllowAnon,    // Allow anyone
    //MetadataDebugAdminRole = null,                   // Default. Do not register /metadata/debug service
});

ServiceStack Filters

Filters for integrating with ServiceStack are available in ServiceStack Filters and Info Filters both of which are pre-registered when registering the TemplatePagesFeature Plugin.

made with by ServiceStack