Liquid Pages
June 23, 2026 · View on GitHub
If you find this project helpful, please consider giving it a ⭐!
LiquidPages is an open-source C# library that brings a Razor Pages–style MVVM framework to Liquid templates. It uses Fluid under the hood and is designed to plug into virtually any .NET web server.
Why?
Most .NET templating solutions are tightly coupled to a specific web server or framework. LiquidPages was built to solve two problems:
- Web-server agnostic — the middleware can hook into any web server (EmbedIO, a custom HTTP listener, or anything else you can hand a request to), so you aren't locked into a particular host.
- Modular by design — Liquid templates and their page models can live in any C# project across a solution. Each route defines its own
IFileProvider, making it straightforward to split pages across multiple projects and compose them at runtime.
The result is a lightweight, portable templating layer that stays out of your way regardless of how your application is structured.
Setup
1. Install the NuGet package
nuget add Kinetq.LiquidPages
2. Register services
services.AddLiquidPages();
3. Register routes, filters, and template file providers at startup
Inject ILiquidStartup and call the registration methods during your application's startup phase:
_liquidStartup.RegisterPageModels();
_liquidStartup.RegisterFilters();
_liquidStartup.RegisterFileProvider("/", fileProvider);
RegisterFileProvider is how template options are now registered for each route prefix. This is required so LiquidPages can resolve templates from the correct source (physical files, embedded files, etc.).
It should always be AFTER RegisterPageModels so page model types are available when the provider is registered.
See the sample projects for concrete startup usage:
src/Kinetq.LiquidPages.AspNetCore.Sample/Program.cssrc/Kinetq.LiquidPages.EmbedIO.Sample/Program.cssrc/Kinetq.LiquidPages.GenHTTP.Sample/Program.cssrc/Kinetq.LiquidPages.SimpleW.Sample/Program.cs
4. Create a page model
Decorate your page model with [LiquidPage], providing a route regex and template path:
[LiquidPage("/", "Pages/Home.liquid")]
public class HomeModel : LiquidPageModel
{
public string Title { get; set; } = "Welcome to Home";
public override Task OnGetAsync(LiquidRequestModel request)
{
return Task.CompletedTask;
}
}
5. Create the Liquid template
Properties on the page model are available in the template via the view_model object:
{% capture page_content %}
<h1>{{ view_model.title }}</h1>
{% endcapture %}
{% include 'Layouts/default.liquid' %}
6. Wire up middleware
GenHTTP middleware
Install the GenHTTP companion package:
dotnet add package Kinetq.LiquidPages.GenHTTP
Resolve ILiquidStartup, ILiquidResponseMiddleware, and ILiquidRoutesManager from your container, register routes/file providers, then attach LiquidHandlerBuilder to your layout:
var middleware = serviceProvider.GetRequiredService<ILiquidResponseMiddleware>();
var routesManager = serviceProvider.GetRequiredService<ILiquidRoutesManager>();
var startup = serviceProvider.GetRequiredService<ILiquidStartup>();
startup.RegisterPageModels();
startup.RegisterFileProvider("/", new EmbeddedFileProvider(typeof(Program).Assembly));
var staticResources = Resources.From(ResourceTree.FromDirectory(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Static")));
var app = Layout.Create()
.Add("Static", staticResources)
.Add(new LiquidHandlerBuilder(middleware, routesManager));
await Host.Create()
.Handler(app)
.Bind(IPAddress.Any, 8080)
.RunAsync();
LiquidHandlerBuilder implements IHandlerBuilder<LiquidHandlerBuilder>, so you can attach any GenHTTP concern (compression, caching, CORS, etc.) before the handler is built. See the full GenHTTP documentation for a complete walkthrough.
EmbedIO middleware
Install the EmbedIO companion package and attach LiquidPages to your WebServer:
dotnet add package Kinetq.LiquidPages.EmbedIO
var startup = serviceProvider.GetRequiredService<ILiquidStartup>();
startup.RegisterFileProvider("/", new EmbeddedFileProvider(typeof(Program).Assembly));
startup.RegisterPageModels();
var middleware = serviceProvider.GetRequiredService<ILiquidResponseMiddleware>();
var routesManager = serviceProvider.GetRequiredService<ILiquidRoutesManager>();
webServer.WithLiquidPages(middleware, routesManager);
If you need lower-level control, LiquidWebModule now takes ILiquidRoutesManager in its constructor:
webServer.WithModule(new LiquidWebModule("/", routesManager)
{
LiquidResponseMiddleware = middleware
});
ASP.NET Core middleware
Install the ASP.NET Core companion package:
dotnet add package Kinetq.LiquidPages.AspNetCore
Register LiquidPages services, initialize startup registrations, and map LiquidPages middleware directly on the app:
using Kinetq.LiquidPages.AspNetCore;
using Kinetq.LiquidPages.Helpers;
using Kinetq.LiquidPages.Interfaces;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLiquidPages(typeof(Program).Assembly);
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var startup = scope.ServiceProvider.GetRequiredService<ILiquidStartup>();
startup.RegisterPageModels();
startup.RegisterFileProvider("/", new EmbeddedFileProvider(typeof(Program).Assembly));
}
app.UseLiquidPagesErrorHandling();
app.UseStaticFiles();
app.UseLiquidPages();
await app.RunAsync();
SimpleW middleware
Install the SimpleW companion package:
dotnet add package Kinetq.LiquidPages.SimpleW
Resolve ILiquidRoutesManager, ILiquidResponseMiddleware, and ILiquidStartup from your container, then register page models/file providers before attaching LiquidPagesModule:
using Kinetq.LiquidPages.Helpers;
using Kinetq.LiquidPages.Interfaces;
using Microsoft.Extensions.FileProviders;
using SimpleW;
var liquidRoutesManager = serviceProvider.GetRequiredService<ILiquidRoutesManager>();
var liquidResponseMiddleware = serviceProvider.GetRequiredService<ILiquidResponseMiddleware>();
var liquidStartup = serviceProvider.GetRequiredService<ILiquidStartup>();
liquidStartup.RegisterFileProvider("/", new EmbeddedFileProvider(typeof(Program).Assembly));
liquidStartup.RegisterPageModels();
var server = new SimpleWServer(IPAddress.Any, 2015);
server.UseStaticFilesModule(options => {
options.Path = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Static");
options.Prefix = "/Static";
options.CacheTimeout = TimeSpan.FromDays(1);
options.AutoIndex = true;
});
server.UseModule(new LiquidPagesModule(liquidRoutesManager, liquidResponseMiddleware)
{
MapFallback404 = true
});
await server.RunAsync();
For other web servers, call HandleRequestAsync on ILiquidResponseMiddleware from within your own request handler.
Visual Studio Extension
Install the Kinetq.LiquidPages.Extension from the Visual Studio Marketplace for syntax highlighting, a Prettier-based formatter (Ctrl+Shift+X), and quick commands to scaffold new pages.
Before using the Add LiquidPage command, install the templates package:
dotnet new install Kinetq.LiquidPages.Templates
The extension automatically adds a .filenesting.json to your project, which nests .liquid.cs code-behind files under their corresponding .liquid template in Solution Explorer.
Without the extension
If you choose not to use the extension, you will need to configure the following manually.
.filenesting.json — add this to your project root to nest .liquid.cs files under their .liquid counterparts:
{
"root": true,
"dependentFileProviders": {
"add": {
"extensionToExtension": {
"add": {
".liquid": [ ".liquid.cs" ]
}
}
}
}
}
.vs/VSWorkspaceSettings.json — add this to suppress HTML validation warnings inside .liquid files and enable HTML syntax highlighting for them in Visual Studio:
{
"HtmlValidation.IgnorePatterns": [
"**/*.liquid"
]
}
Documentation
Full documentation: https://www.kinetq.com/docs/open-source-software/liquid-pages