Value Convertors

In the prior section, we created an AwesomeValue object and set it in the Context. However, you can also push any object to the Context:

context.SetValue("name", "Deane");

How can we do this, if we’ve established that Fluid will only work with FluidValue objects?

Because before Fluid adds that to a Scope dictionary, it works very hard to convert into an appropriate FluidValue object.

First, there are a series of SetValue extension methods for common types – one of them takes a String and turns it into a StringValue, etc. The C# engine will always call the most specific method it can find, and this will usually generate the appropriate FluidValue.

But if there’s no matching extension method, Fluid has a catch-all that will do the following.

  1. It will serially invoke the custom value converters that you can set on TemplateOptions.ValueConverters. If any of these return a FluidValue, Fluid will use the result. If any of them return anything else, it will skip to Step 2 and try to convert the result there. If a converter returns null, Fluid will try the next converter in the list.
  2. After the list of custom converters, Fluid has several common-sense hard-coded conversions. Strings turn into StringValues, numbers of all kinds turn into NumberValues, etc. If this works, Fluid will use the result.
  3. For everything else, Fluid will wrap the value in an ObjectValue and use that.

The end result is that everything in a Scope dictionary will be some kind of FluidValue object.

A converter is just a short function that takes any value and returns one of three things:

  1. A FluidValue object, meaning it was successfully converted
  2. Some other object type, meaning this new value should continue running through the custom converters. This allows us to do “chained conversions.” This gets a little confusing, but if we want to get A and B to D, we can convert them both to C then have a single conversion from C to D
  3. null, meaning this filter didn’t apply, so continue running the existing value through the custom converters

For example, if we decided that all instances of Person should be AwesomeValue objects, using the person’s name, we could do this:

TemplateOptions.Default.ValueConverters.Add((v) => {
    if(v is Person person)
    {
        return new AwesomeValue(person.Name);
    }
    return null;
}

Now we can pass Person objects into the Context and they will be auto-converted to AwesomeValue objects before being placed into a Scope.

var deane = new Person() { Name = "Deane" });
context.SetValue("person", deane);
{{ person }}
Deane is awesome.