Custom Filters
You can write your own filters. They’re just C# methods (a “backing method”) that take in a set of parameters and return a FluidValue
. These methods are mapped to strings which are used to invoke filter.
Consider this custom filter that appends “ is awesome” to whatever it’s invoked on.
ValueTask<FluidValue> IsAwesome(FluidValue input, FilterArguments arguments, TemplateContext context)
{
return new StringValue(input.ToStringValue() + " is awesome");
}
The incoming parameters:
- input: This is a
FluidValue
representing whatever the filter is being applied to – the thing that’s to the left of the “pipe.” A filter will likely always use this in some form, since it literally exists to change this value. - arguments: A collection of all the arguments provided to the filter (see Filters for the syntax). Filters aren’t input-checked, so anything can be passed in. It’s up to the filter to validate the arguments and use what it wants – there are methods for this, like
IsInteger()
. These can be accessed by ordinal (arguments.At(0)
) or by key (for named arguments;arguments["format"]
). Arguments are optional; some filters won’t need them. - context: The executing
TemplateContext
. You’ll often need this to iterate arrays (see the last example on the page).
We need to return a FluidValue
from the method. It doesn’t matter what we return or how we decide what it should be – that’s up to the internal logic of the filter.
Once we’ve written the backing method, we need to map it to a string so it can be invoked:
TemplateOptions.Default.Filters.AddFilter("is_awesome", IsAwesome);
(Note: we can register a filter with dots, but we can’t call it. If we try to “namespace” our filter by registering it under something like my_filters.filter_name
, we’ll get a parser error when we try to use it. There is an open issue for this, but it has not been identified as something that will change.)
This mapping is done one time (TemplateOptions
is a singleton, remember), and is global to all template executions.
Once the filter is mapped, we can call it like this:
{% assign name = "Deane" %}
{{ name | is_awesome }}
“Deane” will be passed into the IsAwesome
method as a StringValue
for the input
parameter.
Deane is awesome
In practice, it’s likely easier to define filters using anonymous methods and register them in the same operation.
TemplateOptions.Default.Filters.AddFilter("is_awesome", (i, a, c) =>
{
return new StringValue(i.ToStringValue() + " is awesome");
});
Here’s an example of a filter that uses arguments.
It will create an “Oxford List” from an array, which means a comma-delimited list with a conjunction preceding the last element. We’ll presume “and,” but let the template developer pass a different conjunction as an argument.
public ValueTask<FluidValue> OxfordList(FluidValue input, FilterArguments arguments, TemplateContext context)
{
if (input.Type != FluidValues.Array)
{
return NilValue.Instance;
}
var conjunction = arguments.At(0).Or(StringValue.Create("and")).ToStringValue();
var values = input.Enumerate(context).Select(v => v.ToStringValue()).ToList();
values[values.Count() - 1] = string.Concat(conjunction, " ", values[values.Count() - 1]);
return StringValue.Create(string.Join(", ", values));
}
TemplateOptions.Default.Filters.AddFilter("oxford_list", OxfordList);
Note the usage of arguments.At(0).Or()
to specify a default if no argument was provided.
{% assign children = "Alec,Gabrielle,Isabella" | split:"," %}
{{ children | oxford_list }}
{{ children | oxford_list:"or" }}
{{ children | oxford_list:"but not" }}
Alec, Gabrielle, and Isabella
Alec, Gabrielle, or Isabella
Alec, Gabrielle, but not Isabella
FilterArguments
provides several extension methods to check for and manipulate arguments.
arguments.At(0); // Get the first argument
arguments.HasNamed("argument name"); // Check for a named argument
arguments.At(0).Or("default value"); // Get the first argument or a default
arguments.Names; // Get the keys of named arguments
arguments.At(0).IsInteger() // Ensure the argument is an integer