Liquid Pages
Liquid pages is a library that uitlizes Fluid under the hood to create middleware that can be used with any web server to render liquid templates. It has a MVVM philosophy similar to Razor Pages (hence the name Liquid Pages).
Setup
Start by adding the nuget package:
nuget add Kinetq.LiquidPages
Next add all of the Liquid Pages services via this helper method:
services.AddLiquidPages()
Next you'll add middleware, which could be different based on the web server you are using. Currently there is only one build in middleware for EmbedIO (although building your own is very simple and detailed below).
Startup
For Liquid Pages to properly function Routes, Filters and Types need to be registered before attempting to render a template. In order to do that you will need to resolve the ILiquidStartup service at any point after the DI container has been created and before the application has started up. Then call the RegisterPageModels and RegisterFilters methods.
public class WebsiteStartup : IStartup
{
private readonly ILiquidStartup _liquidStartup;
public WebsiteStartup(ILiquidStartup liquidStartup)
{
_liquidStartup = liquidStartup;
}
public async Task Execute()
{
await _liquidStartup.RegisterPageModels();
await _liquidStartup.RegisterFilters();
}
}
Important! Even after these two steps have been completed, you'll need to setup some form of milddeware, you can view an example of that in the Sample project created here.
File Resolution
More than likely you'll want to set up your own form of file resolution for your .liquid files and assets. You can do this by creating a BasePage and overriding the GetFileProvider method:
using Kinetq.LiquidPages.Pages;
using Microsoft.Extensions.FileProviders;
namespace Kinetq.Website.Pages;
public class BasePage : LiquidPageModel
{
public string Title { get; set; }
public string SeoTitle { get; set; }
public string SeoDescription { get; set; }
public override IFileProvider GetFileProvider()
{
#if DEBUG
string workingDirectory = Directory.GetCurrentDirectory();
string projectDirectory = Directory.GetParent(workingDirectory).Parent.Parent.FullName;
return new PhysicalFileProvider(projectDirectory);
#else
return = new EmbeddedFileProvider(typeof(BasePage).Assembly, "Kinetq.Website");
#endif
}
}
The GetFileProvider base implementation currently defaults to a physical file provider.
LiquidPageModel
The LiquidPageModel has three overriding members:
OnGetAsync
OnPostAsync
GetFileProvider
Also any properties defined on the page model will be made available in the .liquid template, exposed off of the view_model object passed into the templating engine.
Here is an example of the PageModel:
[LiquidPage("^/$", "Pages/Home.liquid")]
public class HomeModel : LiquidPageModel
{
public string Title { get; set; } = "Welcome to Home";
public override Task OnGetAsync(LiquidRequestModel request)
{
// Initialize your model properties here
// This method is called when the page is requested
return Task.CompletedTask;
}
}
Here is an example of the template:
{% capture page_content %}
<h1>{{ view_model.title }}</h1>
{% endcapture %}
{% include 'Layouts/default.liquid' %}
Notice that the page model needs to be decorated with [LiquidPage("^/$", "Pages/Home.liquid")], where the first argument is regex used to define the route pattern and the second argument is the path to the liquid template. With the default GetFileProvider implementation, the route of the path will always start at the csproj root.
LiquidErrorPage
Similarly to the LiquidPage, you can define a LiquidErrorPage that will be used based on HttpStatusCode Exceptions thrown during the rendering process:
[LiquidErrorPage(HttpStatusCode.NotFound, "ErrorPages/NotFound.liquid")]
public class NotFoundModel : LiquidPageModel
{
public string Title { get; set; } = "Page Not Found";
public string NotFoundMessage { get; set; } = "The page you are looking for was not found.";
public override Task OnGetAsync(LiquidRequestModel request)
{
// Initialize your model properties here
// This method is called when the page is requested
return Task.CompletedTask;
}
}
Notice the only difference here is the attribute used to decorate the page model, specifying the status code instead of a route pattern.
