Skip to content

DotLiquid for Developers

pekspro edited this page May 2, 2022 · 24 revisions

First steps

Getting started with DotLiquid is very easy. A DotLiquid template is rendered in two steps: Parse and Render. For an overview of the DotLiquid syntax, please read DotLiquid for Designers.

	Template template = Template.Parse("hi {{name}}");  // Parses and compiles the template
	template.Render(Hash.FromAnonymousObject(new { name = "tobi" })); // Renders the output => "hi tobi"

The Parse step creates a fully compiled template which can be re-used. You can store it in memory or in a cache for faster rendering later.

All parameters you want to use in your DotLiquid templates have to be passed as parameters to the Render method. DotLiquid does not know about your C# (or VB.NET) local or instance variables.

Configuration properties

Property Default Description
Liquid.UseRubyDateFormat false Toggles between the DotLiquid default (.NET) and the Liquid default (Ruby) date formats for template rendering.
Template.DefaultSyntaxCompatibilityLevel DotLiquid20 Applies default syntax compatibility for template rendering.
See DotLiquid Syntax Compatibility
Template.FileSystem BlankFileSystem Enables a file system location to be provided for storage of included templates
Template.NamingConvention RubyNamingConvention Naming convention used for template parsing (CSharp or Ruby).
See DotLiquid Syntax Compatibility#naming-convention
Template.RegexTimeOut 10 TimeOut used for all Regex in DotLiquid

Syntax Compatibility

Main Article: DotLiquid Syntax Compatibility

Due to practical and historical considerations, there are differences between the .NET implementation and the official Ruby implementation. Since many of these differences can be dictated by development, it is important to properly configure your environment with your preferences and communicate those with designers.

Rules for template rendering parameters

Objects that you pass to Template.Render must satisfy one of the following conditions:

  • Type is an integral type (int, string, decimal, etc.), list or array of integral types, or anonymous type
  • Class inherits from Drop (more info)
  • Class implements ILiquidizable
  • Class is decorated with [LiquidType] attribute.
    • Note that you must specify the allowed member names. For example: [LiquidType("AllowedMember1", "AllowedMember2")].
    • All members are allowed if class is decorated with [LiquidType("*")]. Partial matches are not supported (e.g. *Name)
  • Type is registered with Template.RegisterSafeType(Type type, string[] allowedMembers)
  • Type is registered with Template.RegisterSafeType(Type type, Func<object, object> func)

Note: Nested objects do not inherit their parent's rules in any case. Refer to above list to expose the child's properties.

Extending DotLiquid

Extending DotLiquid is very easy. If you do create useful filters or tags, please consider creating a pull request to this repository. Refer to our Contributing Guidelines for more information.

Additional filters

Main Article: Additional Filters

There are additional filters available in DotLiquid over and above those specified by the Liquid syntax, take a look at the main article for details.

Create your own filters

Creating filters is very easy. Filters are just methods which take one parameter and return a modified string. You can use your own filters by passing an array of filter types to the Render call like this: template.Render(new RenderParameters(CultureInfo.CurrentCulture) { Filters = new[] { typeof(MyTextFilters), typeof(MyDateFilters) }) });

	public static class TextFilter
	{
		public static string Textilize(string input)
		{
			return TextileFormatter.FormatString(input);
		}

		public static string RegexReplace(string input, string pattern, string replacement)
		{
		    return Regex.Replace(input, pattern, replacement);
		}
	}

	Template template = Template.Parse(" {{ '*hi*' | textilize }} ");
	template.Render(new RenderParameters(CultureInfo.CurrentCulture) 
                    {
                        Filters = new System.Type[] { typeof(TextFilter) }
                    });  // => "<b>*hi*</b>" 

        template = Template.Parse(" {{ 'red is green' | regex_replace pattern: '(red|green)' replacement: 'blue' }}");
	template.Render(new RenderParameters(CultureInfo.CurrentCulture) 
                    {
                        Filters = new System.Type[] { typeof(TextFilter) }
                    }); // => "blue is blue"

Alternatively, you can register your filters globally:

	public static class TextFilter
	{
		public static string Textilize(string input)
		{
			return TextileFormatter.FormatString(input);
		}

		public static string RegexReplace(string input, string pattern, string replacement)
		{
		    return Regex.Replace(input, pattern, replacement);
		}
	}

	Template.RegisterFilter(typeof(TextFilter));

Once the filter is globally registered, you can simply use it. By default, filter names in liquid markup are snake_case.

	Template template = Template.Parse(" {{ '*hi*' | textilize }} ");
	template.Render(); // => "<b>*hi*</b>" 

        template = Template.Parse(" {{ 'red is green' | regex_replace pattern: '(red|green)' replacement: 'blue' }} ");
	template.Render(); // => "blue is blue" 

A filter can access the current context if you add a Context object as the first argument to your filter method. DotLiquid will automatically pass the current context to your filter:

	public static String MyFilter(Context context, String input)
	{
		//...
	}

Filters also work from F#:

	open DotLiquid

	type TextFilter() =
		static member Textilize (input : string) =
			"<b>" + input + "</b>"

	Template.RegisterFilter(TextFilter().GetType());
	let template = Template.Parse(" {{ '*hi*' | textilize }} ");
	printfn "%s" (template.Render()) // => "<b>*hi*</b>"

Please note that currently method overloading is not supported with filters. A workaround is to use different names for your methods.

Create your own tags

To create a new tag, simply inherit from DotLiquid.Tag and register your tag with DotLiquid.Template.

	public class Random : DotLiquid.Tag
	{
		private int _max;

		public override void Initialize(string tagName, string markup, List<string> tokens)
		{
			base.Initialize(tagName, markup, tokens);
			_max = Convert.ToInt32(markup);
		}
		
		public override void Render(Context context, TextWriter result)
		{
			result.Write(new Random().Next(_max).ToString());
		}
	}

	Template.RegisterTag<Random>("random");

	Template template = Template.Parse(" {% random 5 %}");
	template.Render(); // => "3"

Create your own tag blocks

All tag blocks are parsed by DotLiquid. To create a new block, you just have to inherit from DotLiquid.Block and register your block with DotLiquid.Template.

	public class Random : DotLiquid.Block
	{
		private int _max;

		public override void Initialize(string tagName, string markup, List<string> tokens)
		{
			base.Initialize(tagName, markup, tokens);
			_max = Convert.ToInt32(markup);
		}
		
		public override void Render(Context context, StreamWriter result)
		{
			if (new System.Random().Next(_max) == 0)
				base.Render(context, result);
		}
	}

	Template.RegisterTag<Random>("random");

	string text = " {% random 5 %} wanna hear a joke? {% endrandom %} ";
	Template template = Template.Parse(text);
	template.Render(); // => In 20% of the cases, this will output "wanna hear a joke?"