Templating
Wisp uses the Fluid templating engine, which is a C# implementation of the Shopify Liquid templating language. Fluid is mostly fully compatible with Liquid but we recommend using this unofficial Liquid documentation rather than the Fluid documentation.
Loading Templates
By default, Wisp looks for template files in the Templates/ directory in the current
CWD.
Template files must have the .liquid extension. This is for compatibility reasons as
most modern IDEs support the Liquid language and the .liquid extension but most are not
aware of the Fluid language and if templates used .html or .fluid, you would potentially
lose syntax highlighting and other IDE features.
Warning
When running your application with dotnet run, the CWD for the process is the
directory you launched it from.
When launching from an IDE such as Rider or Visual Studio, the CWD is usually set to
the output directory (bin/Debug/net10.0) and so you need to set your template files
to CopyAlways to copy them to the output directory and keep them up to date.
You can do this automatically for all files in the Templates directory by putting
this snippet in your .csproj file.
<ItemGroup>
<Content Include="Templates/**/*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
That being said, we recommend using dotnet run, it makes reloading templates
much easier.
Template Hot-Reload
When running in Debug mode (e.g. with dotnet run), Wisp loads the templates from disk
for each request, so when you change a template file while the application is running, the
next request will use the updated version. No further action or configuration is required.
In Release mode, Wisp caches templates aggressively so to apply changes, you will need to
restart the application.
Passing Data to Templates
When you return a ViewResult from a controller, you can optionally pass data to the template.
The data can be any object. A convenient concept to use here is
anonymous types.
All public fields, properties and methods in the passed-in object are available in a template. Keep
in mind that the object is not passed to the template directly, instead, it's wrapped in a
ViewModel that also provides additional data to the template.
Here is a list of properties available in ViewModel (and therefore in a template).
| Property | Name in Template | Type | Description |
|---|---|---|---|
UserLoggedIn |
user_logged_in |
bool |
Is the current user logged in? |
CurrentUserName |
current_user_name |
string? |
Current user's username |
CurrentUserRoles |
current_user_roles |
List<string> |
Current user's roles |
CurrentUserId |
current_user_id |
string |
Current user's ID |
FlashMessages |
flash_messages |
List<FlashMessage> |
Current Flash Messages |
Model |
model |
object? |
The passed-in model |
Middleware |
middleware |
Dictionary<string, object?> |
Additional Middleware Data |
To align with the naming conventions of the Fluid language, member names are translated to snake_case.
Layout
Every template can optionally use a layout. A layout is a special template that includes an area where the inner template is rendered into.
To create a layout, create a new file, for example _layout.liquid and put {% renderbody %}
somewhere in the file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Wisp</title>
</head>
<body>
{% renderbody %}
</body>
Then, use the layout with the {% layout %} directive. Keep in mind all paths in templates are
relative to the template root (Templates/) and there is no need to explicitly include the .liduid extension.
Partials
You can include partial templates using the {% include %} directive. As with layouts, the
path is relative, and you don't need to specify the extensions.
Included partials have access to the same data as the template that included them.
Macros
The Fluid templating engine supports macros. Macros are basically a way of creating your own template directives.
Warning
Macros are a feature of Fluid included for convenience but generally considered somewhat unsafe and a code smell. We recommend not using macros unless absolutely neccessary (for example when proper recursion is needed).
The general rule of thumb is that if you can't do it in a template without macros, it should probably be done in C#.
See this page for more information about macros.
You can define macros like so:
And then include it and call it like so:
Custom Filters
Warning
Templating configuration is not implemented yet.
In Liquid/Fluid, a filter is a function that transforms data in a template.
For example: {{ 'Hello World' | upcase }} would render as HELLO WORLD.
The Fluid templating engine used in Wisp supports adding custom filters.
To add a custom filter, first create a static function somewhere with the following signature. The function can, but does
not have to be async.
public static ValueTask<FluidValue> MyFilter(FluidValue input, FilterArguments args, TemplateContext context) {}
And then register it with the WispHostBuilder.ConfigureTemplates extension method.