Bug in 4.5.14 templates / filters handling null property values on model

I haven’t tracked this down in the source code yet, so I’m just describing a behavior. It’s inconsistent behavior, at best, but more likely a bug. I also haven’t checked to see if it’s fixed in 5.0 yet.

When creating a template view backed by a model, null model sub-properties aren’t handled right. For example, consider this model structure:

  • Article (template model)
    • Title (string)
    • Lead (string)
    • Body (string)
    • Ad
      • Headline (string)
      • Callout (string)

If I assign an article to the template (Model = article), the first-level string properties are handled properly when they are null. For example, given an article with a null Lead property:

{{ Lead | isNull }}

returns

True

Which is expected.

However, if I try the same thing with an ad that has a null Callout property:

{{ Ad.Callout | isNull }}

the entire expression is just swallowed up. No error… but also no output and the filter never seems to be called.

If Callout has a value (including an empty string), everything works as expected.

Also, I get the same behavior if the object is passed as an Arg. I expected that, since I’m guessing the model behavior is built off the args logic pipeline.

For the moment, I can get around the behavior by guarding everything on the model with null checks. But that’s tedious and error prone.

I hope this makes sense. It took me awhile to track down why my templates weren’t working as expected since the view engine just swallows up the eval.

If I’m doing something wrong, please let me know. But given the inconsistent behavior on very simple tests, I assume this is a deeper issue than user error. And I can’t think of why this might be the expected or as-designed behavior. I’m open to being wrong, however.

I’d love to get feedback on either where I’ve made a mistake, or how I might fix this behavior. Thanks in advance.

Is Ad null, so that it would’ve thrown a NullReferenceException?

If so, the default behavior is to catch NullReferenceException and ArgumentNullException and evaluate the filter to null which emits no value. We’d expect this to be desired behavior in a templating language so accessing deep nested object graphs doesn’t break the page.

You can turn off this behavior by clearing this collection:

TemplateConfig.CaptureAndEvaluateExceptionsToNull.Clear();

Which will then bubble the Exception as normal.

Ad is not null. Only the string property is null.

Again, what is confusing to me is 2 different behaviors for a null string. In fact, I can reproduce each behavior with the exact same null property. If I create a template and pass in a model with a null string property and pass in the exact same model with a null string property as an arg, the model property will properly evaluate to null and call the filter. The exact same property as an arg.property will drop through and not evaluate.

So, again, even if it’s as-designed, the exact same property will give 2 different behaviors depending on how it’s accessed. Which doesn’t seem like the correct behavior.

I’ll try your suggestion and see what it does.

Can you provide some example c# + template code showing the different behavior you’re referring to?

Given this code:

        var sampleModel = new
        {
            StringProperty = "Hello",
            NullStringProperty = (string) null
        };

        var context = new TemplateContext
        {
            Args =
            {
                ["sampleArg"] = sampleModel
            },
            VirtualFiles = VirtualFiles,
            Container = HostContext.Container,
            TemplateFilters =
            {
                new TemplateDefaultFilters()
            }
        }.Init();

        return new PageResult(context.GetPage("Views/test"))
        {
            Model = sampleModel
        };

and this template:

<h2>Model:</h2>
    <p>StringProperty: {{ StringProperty | isNull }}</p>
    <p>NullStringProperty: {{ NullStringProperty | isNull }}</p>
<h2>Arg:</h2>
    <p>sampleArg.StringProperty: {{ sampleArg.StringProperty | isNull }}</p>
    <p>sampleArg.NullStringProperty: {{ sampleArg.NullStringProperty | isNull }}</p>

This is the result (comments added):

Model:
    StringProperty: False [Correct]
    NullStringProperty: True [Correct]

Arg:
    sampleArg.StringProperty: False [Correct]
    sampleArg.NullStringProperty: [Incorrect / different from previous behavior]

I hope this makes more sense. It’s actually a really simple demonstration. It seems like any null string that is more than a single level from a registered arg will exhibit different behavior than a null string as a root property.

This did not change execution or results.

Thanks for the repro, this issue should now be resolved in this commit where it will always call the isNull filter since it’s marked to [HandleUnknownValues].

This change is available from v5 that’s now available on MyGet. Please review the v5 changes before upgrading.

Awesome. Thanks for the super-fast response. I’ll check out the changes.

I’m really enjoying using templates so far. I’ve definitely had to figure out some things that were easy-to-do with my old razor-based code… but I’m doing some way cool stuff I could never do with MVC razor. I have to re-wire my brain a bit, but this is a powerful way to reason about content and pages. Thanks for the mind warp.

1 Like

Awesome! Glad to hear you’re having fun with it :smile:

I upgraded to the latest on MyGet. I get this error even on a bare bones .net core project with ServiceStack (empty-web from GitHub).

Researching it a bit, it commonly comes from mismatched types in libraries. I’m not sure if this is a separate issue from above, or caused by your fix, so I chose to just continue this same thread, assuming it’s the latter.

Yeah looks like it was due to forgotting to build the common libraries CI project after modifying a shared interface. I’ve rebuilt the CI projects again and tested the latest version of MyGet in a new .NET Core App which works.

As the pre-release MyGet packages contains the same 5.0.0 version number you’ll need to clear your NuGet cache to download the latest packages with:

$ nuget locals all -clear

Then you should be able to install the packages as normal.

1 Like