Virtual Members Using GetValue
Remember from the last section that when trying to resolve a member expression, Fluid will call GetValue
on a FluidType
and pass it the member string to get a new FluidValue
back.
Here are two things that do not matter inside GetValue
:
- The subclass of
FluidValue
it returns. So long as it can cast toFluidValue
, this can be anything you like, even something totally different than the type in whichGetValue
is currently executing - The logic of how it gets this value
This means that we can implement “virtual members” – members that return calculated values.
There are a few virtual members already in the library.
{{ name.size }}
{{ children.size }}
{{ children.first }}
(Why two examples of size
? To show that it works on the length of strings in the case of name
, and on the length of enumerables, in the case of children
.)
size
and first
are not “real” members – they’re just member strings that GetValue
detects and uses in its logic.
Let’s return to our AwesomeValue
from above. Remember that when it’s output, it appends “ is awesome” to everything. We might decide we’d like to provide a way to yell this, rather than just say it. Like this:
{{ awesomePerson.yell }}
Assuming our AwesomeValue
wrapped the string “Deane”, this should output:
I, DEANE, AM AWESOME!!!
We can implement AwesomeValue.GetValue
like this:
protected override FluidValue GetValue(string name, TemplateContext context)
{
if(name == "yell")
{
return StringValue.Create($"I, {_value}, AM AWESOME!!!");
}
return NilValue.Instance;
}
Now, Fluid will detect that awesomePerson
is an instance of AwesomeObject
. It will then pass the “yell” member string to GetValue
which will return a StringValue
.
Once Fluid gets the value back, it “forgets” what the original value was. All it knows is that it has a string value now, which means all the GetValue
logic in StringValue
now applies.
So we can do this:
{{ awesomePerson.yell.size }}
(Of course, this sets up some ambiguity between a virtual member and a filter. Why not just do awesomePerson | yell | size
?)
Note that with anything other than “yell”, we return a NilValue
. Why? Because “yell” should be the only member that ever comes after an AwesomeValue
. We just don’t support anything else, so if someone tries to call anything else, they get nothing back.
For a much more practical example, consider this code for XmlValue
which allows you to push an XML document into the context, and search it via a series of virtual members that GetValue
uses internally.
The value of the /person/name/first element: {{ person.name.first._text }}.
The value of the "type" attribute on the name:: {{ person.name._attrs.type }}
Children elements can be iterated by {{ for kid in person.kids._children }}{{ endfor}}
Look through that code, and you’ll set that GetValue
sometimes returns another XmlValue
, but – depending on the member string – might return a StringValue
or an DictionaryValue
.