Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
F

fluid

> 编程语言
Open source

Fluid is an open-source .NET template engine based on the Liquid template language.

1.8K stars0 likes0 views
WebsiteGitHub

About

Fluid is an open-source .NET template engine based on the Liquid template language.

## Basic Overview Fluid is an open-source .NET template engine based on the [Liquid template language](https://shopify.github.io/liquid/). It is a **secure** template language that is also **very accessible** for non-programmer audiences. > This document describes Fluid 3.0, which is under development on `main`. Preview packages are available from the [preview feed](#preview-packages); stable releases are available on [NuGet.org](https://www.nuget.org/packages/Fluid.Core). > To see the corresponding content for v1.0, use [this version](https://github.com/sebastienros/fluid/blob/release/1.x/README.md)
## Tutorials [Deane Barker](https://deanebarker.net) wrote a [very comprehensive tutorial](https://deanebarker.net/tech/fluid/) on how to write Liquid templates with Fluid. For a high-level overview, read [The Four Levels of Fluid Development](https://deanebarker.net/tech/fluid/intro/), which describes different stages of using Fluid.
## Features - Very fast Liquid parser and renderer (no-regexp), with few allocations. See [benchmarks](#performance). - Secure templates by allow-listing all available properties in the template. User templates can't break your application. - Supports **async** filters. Templates can execute database queries more efficiently under load. - Customize filters and tags with your own, even with complex grammar constructs. See [Customizing tags and blocks](#customizing-tags-and-blocks). - Parses templates into a concrete syntax tree that lets you cache, analyze, and alter the templates before they are rendered. - Register any .NET types and properties, or define **custom handlers** to intercept when a named variable is accessed.
## Contents - [Features](#features) - [Using Fluid in your project](#using-fluid-in-your-project) - [Preview packages](#preview-packages) - [NativeAOT and trimming](#nativeaot-and-trimming) - [Source generator](#source-generator) - [Allow-listing object members](#allow-listing-object-members) - [Handling undefined variables](#handling-undefined-variables) - [Execution limits](#execution-limits) - [Converting CLR types](#converting-clr-types) - [Encoding](#encoding) - [Localization](#localization) - [Money filters](#money-filters) - [Time zones](#time-zones) - [Customizing tags and blocks](#customizing-tags-and-blocks) - [ASP.NET MVC View Engine](#aspnet-mvc-view-engine) - [Whitespace control](#whitespace-control) - [Custom filters](#custom-filters) - [Functions](#functions) - [Visiting and altering a template](#visiting-and-altering-a-template) - [Performance](#performance) - [Used by](#used-by)
#### Source ```Liquid
    {% for product in products %}
  • {{product.name}}

    Only {{product.price | price }} {{product.description | prettyprint | paragraph }}
  • {% endfor %}
``` #### Result ```html
  • Apple

    $329 Flat-out fun.
  • Orange

    $25 Colorful.
  • Banana

    $99 Peel it.
``` Notice - The `
  • ` tags are at the same index as in the template, even though the `{% for }` tag had some leading spaces - The `
      ` and `
    • ` tags are on contiguous lines even though the `{% for }` is taking a full line.
      ## Using Fluid in your project You can directly reference the [NuGet package](https://www.nuget.org/packages/Fluid.Core). The code samples in this document assume you have registered the `Fluid` namespace with `using Fluid;`. ### Preview packages After a successful build triggered by a push to `main`, preview packages are published to the [Fluid feed on feedz.io](https://f.feedz.io/sebastienros/fluid/nuget/index.json). Versions follow `3.0.0-preview-`, using the GitHub Actions build run number. These packages contain the latest development changes and are intended for testing before release. Tagged releases continue to be published to NuGet.org. Add the preview feed alongside NuGet.org, then install the latest prerelease version: ```shell dotnet nuget add source https://f.feedz.io/sebastienros/fluid/nuget/index.json --name fluid-preview dotnet add package Fluid.Core --prerelease ``` Keep NuGet.org enabled so dependencies can be restored. If your `NuGet.config` uses package source mapping, also map `Fluid.*` and `MinimalApis.LiquidViews` to the `fluid-preview` source. ### Hello World #### Source ```csharp var parser = new FluidParser(); var model = new { Firstname = "Bill", Lastname = "Gates" }; var source = "Hello {{ Firstname }} {{ Lastname }}"; if (parser.TryParse(source, out var template, out var error)) { var context = new TemplateContext(model); Console.WriteLine(template.Render(context)); } else { Console.WriteLine($"Error: {error}"); } ``` #### Result `Hello Bill Gates` ### Model security Fluid templates can read public properties and fields from the model and from objects reachable through it. This is by design; a model passed to `TemplateContext` should be treated as the template's readable data boundary. When rendering an untrusted template, pass a dedicated model that contains only the data the template is allowed to read. Do not pass domain entities, service objects, configuration objects, or other object graphs that may expose sensitive data through public members. ### Thread-safety A `FluidParser` instance is thread-safe and should be shared by the whole application. A common pattern is to declare the parser in a local static variable: ```c# private static readonly FluidParser _parser = new FluidParser(); ``` An `IFluidTemplate` instance is thread-safe and can be cached and reused by multiple threads concurrently. A `TemplateContext` instance is __not__ thread-safe, and a new instance should be created every time an `IFluidTemplate` instance is used. Values registered in `TemplateOptions.GlobalValues` are shared by every context created from those options. Values set directly on a `TemplateContext` belong to that rendering. Custom tags that need temporary values should use a scope lease: ```csharp using var scope = context.EnterScope(ScopeBehavior.Local); context.SetValue("temporary", value); ``` `Local` scopes inherit values and keep assignments local. `WriteThrough` scopes keep values assigned with `LocalScope.SetOwnValue` temporary while normal assignments update the caller, which matches `include` and loop behavior. `Isolated` scopes can only read the context's initial values and `GlobalValues`, which matches the `render` tag.
      ## NativeAOT and trimming Fluid works when targeting NativeAOT and trimmed deployments. - If dynamic code is not supported at runtime, Fluid automatically switches to reflection-based member accessors. - Runtime `MemberAccessStrategy.Register` APIs are available for custom mappings. - No interceptor setup is required. ### Recommended usage when targeting NativeAOT 1. Reuse `TemplateOptions` instances (for example, at app startup). 2. If you use runtime `MemberAccessStrategy.Register` calls, execute them during application startup before rendering templates. 3. Pass a statically typed model and custom options to `TemplateContext`, or use `[FluidRegister]` for types that are not visible at a context construction site. 4. Validate your app with AOT/trim publish settings: ```shell dotnet publish -c Release -r -p:PublishAot=true ``` ### Compatibility boundaries NativeAOT compatibility and trimming compatibility are related but separate. Fluid's reflection fallback does not emit code and can run when dynamic code is unavailable. A trimmed application must also preserve every member that the fallback discovers at runtime. | Usage | NativeAOT with trimming | | --- | --- | | `new TemplateContext(concreteModel, customOptions)` with the source generator enabled and matching compile-time and runtime model types | Compatible. Eligible public fields and properties are accessed directly by generated code. | | A model registered with `[FluidRegister]` | Compatible. Use this for boxed models, nested model types, models created in another assembly, or types not visible at a `TemplateContext` construction site. | | An explicit `MemberAccessStrategy.Register` mapping or custom `MemberAccessor` that accesses members directly | Compatible. The application supplies the access logic instead of relying on member discovery. | | The one-argument `new TemplateContext(model)` constructor or `TemplateOptions.Default` | No accessor is inferred. Rendering uses an explicit registration if one exists; otherwise it falls back to reflection. | | A model passed as `object`, an interface or base type whose runtime type differs, or an unregistered nested model | No accessor is inferred for the runtime type. Use `[FluidRegister]` for the concrete runtime type or register an accessor explicitly. | | Reflection fallback for an unregistered type | NativeAOT-compatible only when the required public member metadata is preserved from trimming. Prefer source generation or explicit registration rather than relying on linker configuration. | | A reflection-discovered `Task` member | Avoid in trimmed NativeAOT applications because the reflection fallback uses runtime dynamic binding to read the result. A generated or custom accessor handles `Task` without dynamic binding. | Source generation covers public readable properties and public fields that can be referenced from generated code. Members that cannot be generated continue through the normal registration and reflection fallback paths. If any required member uses a fallback path, validate the published application rather than assuming that source generation preserved it. ### Source generation (optional) When the `Fluid.SourceGenerator` analyzer is enabled, Fluid can generate strongly-typed member accessors for model types discovered at compile time. The model type is inferred automatically when its compile-time and runtime types match and it is passed with custom options: ```csharp var options = new TemplateOptions(); var context = new TemplateContext(person, options); ``` The generated accessor is activated on the `DefaultMemberAccessStrategy` of the options instance passed to that constructor. Explicit registrations on `options.MemberAccessStrategy` still take precedence. The one-argument `TemplateContext(model)` constructor does not infer or activate a model accessor because it uses the shared `TemplateOptions.Default` instance. Use `FluidRegisterAttribute` when a model is passed as `object`, is created outside the compilation using the source generator, or when nested model types also need generated accessors. The recommended explicit pattern is to declare a custom `TemplateOptions` subclass and add one attribute per model type: ```csharp using Fluid; [FluidRegister(typeof(Person))] [FluidRegister(typeof(Address))] public partial class PublicTemplateOptions : TemplateOptions { } ``` Use the generated options type like any other `TemplateOptions` instance: ```csharp var options = new PublicTemplateOptions(); ``` The generated registrations are instance-scoped and are applied automatically to each `PublicTemplateOptions` instance. Runtime registrations still work and can be added normally: ```csharp options.MemberAccessStrategy.Register((product, name) => product.Name); ``` ### Custom member accessors Custom accessors derive from `MemberAccessor` and return a `ValueTask` directly. ```csharp private sealed class ProductDisplayNameAccessor : MemberAccessor { public override ValueTask GetAsync( object obj, string name, TemplateContext context) { return CreateValueTask(((Product)obj).Name, context); } } options.MemberAccessStrategy.Register( "display_name", new ProductDisplayNameAccessor()); ``` T
  • GitHub Issues· 0 open

    View all on GitHub

    No open issues yet, or sync has not completed.

    Highlights

    • •Very fast Liquid parser and renderer (no-regexp), with few allocations. See benchmarks.
    • •Secure templates by allow-listing all available properties in the template. User templates can't break your application.
    • •Supports async filters. Templates can execute database queries more efficiently under load.
    • •Customize filters and tags with your own, even with complex grammar constructs. See Customizing tags and blocks.
    • •Parses templates into a concrete syntax tree that lets you cache, analyze, and alter the templates before they are rendered.
    • •Register any .NET types and properties, or define custom handlers to intercept when a named variable is accessed.
    • •Features
    • •Using Fluid in your project
    • •Preview packages
    • •NativeAOT and trimming

    > Tags

    C#dotnetliquidparsershopify

    No comments yet. Be the first to share.

    > Details

    PublishedAug 1, 2026
    UpdatedSep 17, 2026
    Category编程语言
    PricingOpen source

    > Related tools

    T
    TypeScript
    JavaScript 的超集,为前端与全栈提供静态类型
    P
    Python
    通用编程语言,广泛用于 Web、数据与 AI
    G
    Go
    Google 推出的简洁高效系统语言