Skip to content
Mike Bridge edited this page Aug 12, 2017 · 17 revisions

Updated for version 0.9.6

A Simple Template

Here's a simple template that renders a variable in HTML:

<div>{{myvariable}}</div>

The C# code to render this template in Liquid.NET looks like this:

// The TemplateContext stores values that the template can render 
// (along with other values).
ITemplateContext ctx = new TemplateContext();

// Any values that you want to put into the TemplateContext in Liquid.NET 
// are put into wrappers.  For example, the call to the static factory 
// LiquidString.Create(String value) will create a string-wrapper of the 
// type LiquidString.
ctx.DefineLocalVariable("myvariable", LiquidString.Create("Hello World!"));

// You can generate a template with "LiquidTemplate.Create(String template)".
// Values that are rendered are wrapped in double-parentheses: "{{" and "}}".
var parsingResult = LiquidTemplate.Create("<div>{{myvariable}}</div>");
Console.WriteLine(parsingResult.LiquidTemplate.Render(ctx).Result);

==> "<div>Hello World!</div>"

Render a Simple Collection with a "For" Tag

Liquid can render a "collection" of values like this:

<ul>
{% for myitem in mycollection %}
   <li>{{ myitem }}</li>
{% endfor %}
</ul>
ITemplateContext ctx = new TemplateContext();

// You can create an array using the IEnumerable array-creation literal.
// Here, each element is a numeric type.
ctx.DefineLocalVariable("mycollection", new LiquidCollection{{ 
   LiquidNumeric.Create(2),
   LiquidNumeric.Create(4),
   LiquidNumeric.Create(6)
}};

// A tag uses the delimiters "{%" and "%}".
var parsingResult= LiquidTemplate.Create("<ul>{% for item in items %}" +
    "<li>{{item}}</li>" +
    "{% endfor %}</ul>");
Console.WriteLine(parsingResult.LiquidTemplate.Render(ctx).Result);

==> "<ul><li>2</li><li>4</li><li>6</li></ul>"

Here we used a Liquid tag. A tag usually wraps html in some way. In this case, it wraps the contents of a for loop. A tag may have no closing delimiter, but when it does, it will look like {% end____ %}.

Render an Object in a Template

In Liquid.NET, an object is represented by a LiquidHash (which implements IDictionary). If you're wondering where liquid "drops" are, you can stop looking---they aren't necessary in .Net.

ITemplateContext ctx = new TemplateContext();

// Create a hash with two fields, "first" and "last"
var nameHash = new LiquidHash
{
    {"first", LiquidString.Create("Tobias")},
    {"last", LiquidString.Create("Lütke")}
};

// Create a hash with two fields---one string, and one hash.
ctx.DefineLocalVariable("greeting", new LiquidHash
{
    {"address", LiquidString.Create("Hello")},
    {"name", nameHash}
});

// You can reference arbitrarily nested hashes using dots
var parsingResult= LiquidTemplate.Create("You said '{{ greeting.address }} "+ 
    "{{ greeting.name.first }} {{ greeting.name.last }}'");

Console.WriteLine(parsingResult.LiquidTemplate.Render(ctx).Result);

==> "You said 'Hello Tobias Lütke'"

Alter a Value with a Filter

You can transform a value with a filter. A value is sent to a filter via a vertical pipe, and a filter may take some arguments after a colon, e.g. {{ myvalue | myfilter: myargs }}

{{ resultcount }} {{ resultcount | pluralize: 'item', 'items' }} were found 
for '{{searchterm | downcase}}'.
// TemplateContext can be initialized fluently.
// Add .WithAllFilters() to include the filters.
ITemplateContext ctx = new TemplateContext()
        .WithAllFilters()
        .DefineLocalVariable("resultcount", LiquidNumeric.Create(42))
        .DefineLocalVariable("searchterm", LiquidString.Create("MiXeDcAsE"));

var parsingResult= LiquidTemplate.Create("{{ resultcount }} " +
    "{{ resultcount | pluralize: 'item', 'items' }} were found " +
    "for '{{searchterm | downcase}}'.");

Console.WriteLine(parsingResult.LiquidTemplate.Render(ctx).Result);

=> "42 items were found for 'mixedcase'."

Handle Parsing Errors

There are two kinds of errors in Liquid.NET: parsing errors and rendering errors. These are returned alongside the results produced by LiquidTemplate.Create() and LiquidTemplate.Render().

Parsing errors can occur when you're parsing a liquid template:

var template = "This filter delimiter is not terminated: {{ myfilter";
var parsingResult= LiquidTemplate.Create(template);            
String error = String.Join(",", parsingResult.ParsingErrors
    .Select(x => x.ToString()));
Console.WriteLine(error):

=> "line 1:52 Missing '}}'"

The result here shows that on line 1, character 52, it was expecting to find the matching '}}' for the filter.

Handle Rendering Errors

At rendering time, you can get both rendering and parsing errors. (Parsing errors may come from an include tag.) This code handles just the rendering errors:

ITemplateContext ctx = new TemplateContext().WithAllFilters();
String template = "Divide by zero result in: {{ 1 | divided_by: 0}}";
var parsingResult = LiquidTemplate.Create(template);
var renderingResult = parsingResult.LiquidTemplate.Render(ctx);
String error = String.Join(",", renderingResult.RenderingErrors
    .Select(x => x.Message));

Console.WriteLine("The ERROR was : " + error);
Console.WriteLine("The RESULT was : " + renderingResult.Result);

==> The ERROR was : Liquid error: divided by 0
==> The RESULT was : Divide by zero result in: Liquid error: divided by 0

Note that the template is still parsed, with the error message rendered.

Adding POCOs to the Context

You can put a POCO object and its properties into the context by calling the Object.ToLiquid() extension method. This will recursively descend any objects that your POCO points to—so be careful! You can decorate any property with the attributes [Ignore], [IgnoreIfNull] or [Name("myrenamedfield")], which are self-explanatory. All the property names are in lower case.

// Define the regular C# object
public class MyPoco
{
    public String MyStringField { get; set; }

    public int? MyNullableIntField { get; set; }

    [LiquidName("myrenamedfield")]
    public String MyOtherField { get; set; }

    [LiquidIgnoreIfNull]
    public String MyIgnoreIfNotNullField { get; set; }

    [LiquidIgnore]
    public String MyIgnoredField { get; set; }

    [LiquidIgnoreIfNull]
    public MyPoco NestedPoco { get; set; }
}

// instantiate it
var myPoco = new MyPoco {
    MyStringField = "A string field",
    MyNullableIntField = 123,
    MyOtherField = "Some Other Field",
    MyIgnoredField = "This Shouldn't Show Up",
    MyIgnoreIfNotNullField = null,
    NestedPoco = new MyPoco { MyStringField = "Nested Poco"}
}

// put it in the context
ITemplateContext ctx = new TemplateContext()
    .DefineLocalVariable("poco", myPoco.ToLiquid());

// Parse the template
var parsingResult = LiquidTemplate.Create("Poco Result: {{ poco }}");

// Render the poco by merging it into the template
var renderingResult = parsingResult.LiquidTemplate.Render(ctx);

// See the result
Console.WriteLine(renderingResult.Result);

// the result is reformatted for readability.  the default format is JSON.
==> Poco Result: { "mystringfield" :      "A string field",
                   "mynullableintfield" : 123, 
                   "myrenamedfield" :     "Some Other Field", 
                   "nestedpoco" : { "mystringfield" :      "Nested Poco", 
                                    "mynullableintfield" : null, 
                                    "myrenamedfield" :     null }
                 }