sebastienros / fluid

Fluid is an open-source .NET template engine based on the Liquid template language.
MIT License
1.44k stars 178 forks source link

MemberAccessStrategy.IgnoreCase doesn't work #615

Open apavelm opened 11 months ago

apavelm commented 11 months ago

Fluid.Core version 2.5.0

Ignore Case doesn't work, repro-code below. Is there any option to make it case-insensitive?

public string ProcessTemplate(string templateContent, object substitutionObject)
{
    if (Parser.TryParse(templateContent, out var template, out var error))
    {
        var options = new TemplateOptions
        {
            MemberAccessStrategy =
            {
                IgnoreCasing = true
            }
        };
        var context = new TemplateContext(substitutionObject, options);

        return template.Render(context);
    }
    else
    {
        throw new TemplateServiceException("Error processing template: " + error);
    }
}

public void Test()
{
    var template = "Hello, your {VehicleBrand} with LP number: {LicensePlate} ...";
    var data = new Dictionary<string, object>()
    {
        {"VehiclEbrand", "Toyota"},
        {"LicENsePlate", "ab123cd11"}
    };

Console.WriteLine(ProcessTemplate(template, data)); // result - "Hello, your  with LP number: "
}
apavelm commented 11 months ago

@sebastienros Is it "by design", or it is a bug? Please advise.

hishamco commented 11 months ago

Check this

https://github.com/sebastienros/fluid/blob/f62d8a58b2098989bb59b5fca1c404db10b13f70/Fluid.Tests/TemplateTests.cs#L870-L890

sebastienros commented 11 months ago

Can you add a test case that show it works if you type the correct case {{ p.Firstname }} ?

sebastienros commented 11 months ago

@hishamco I didn't see that was you. Ignore my comment.

@apavelm your template is not valid, it needs two curly braces: {{ VehiclEbrand }}

hishamco commented 11 months ago

No problem Seb :)

@apavelm your template is not valid, it needs two curly braces: {{ VehiclEbrand }}

I know there should be a unit test for it, that's why I let @apavelm discover his bug :)

apavelm commented 11 months ago

@sebastienros Brackets somehow missed from the template here, of course there are double-brackets in the template. Sorry for confusion. The full version of the template looks like:

var template = @"Your {% if VehicleBrand != ""Other"" %} {{VehicleBrand}} {% endif %} {{LicensePlate}} ..."

It works when the case matches, and doesn't work if not , ignoring IgnoreCase setting. Also, could it be the reason of usage Dictionary<string, object> instead of object?

@hishamco on a provided sample you register the type, maybe this is the reason - I don't know. But for my case, anonymous types are essential.

P.S. To confirm the issue before posting it here, I created 2 identical test cases with small difference in Letter-case. And as I wrote above 1 - pass OK (when case matches) , 2 - doesn't (VehicleBrand -> VehiclEbrand)

apavelm commented 11 months ago

For your convinience:


public string ProcessTemplate(string templateContent, object substitutionObject)
{
    if (Parser.TryParse(templateContent, out var template, out var error))
    {
        var options = new TemplateOptions
        {
            MemberAccessStrategy =
            {
                IgnoreCasing = true
            }
        };
        var context = new TemplateContext(substitutionObject, options);

        return template.Render(context);
    }
    else
    {
        throw new TemplateServiceException("Error processing template: " + error);
    }
}

public void Test()
{
    var template = @"Your {% if VehicleBrand != ""Other"" %} {{VehicleBrand}} {% endif %} {{LicensePlate}} ...";

    var data = new Dictionary<string, object>()
    {
        {"VehicleBrand", "Toyota"},
        {"LicensePlate", "ab123cd11"}
    };
    var dataBroken = new Dictionary<string, object>()
    {
        {"VehiclEbrand", "Toyota"},
        {"LicENsePlate", "ab123cd11"}
    };

Console.WriteLine(ProcessTemplate(template, data)); // result - "Your Toyota ab123cd11 ..."
Console.WriteLine(ProcessTemplate(template, dataBroken)); // result - "Your ..."
}
sebastienros commented 11 months ago

Thanks for confirming. I think it's a bug on the custom properties, not the ones coming from the model which are using the membership accessors.

Can you try to pass a dictionary that is initialized with a StringComparison.OrdinalIgnoreCase?

apavelm commented 11 months ago

UPDATE. I additionally added a couple of tests using anonymous and a typed object instead of Dictionary - same result.

Then, I tried all the tests with adding the following line into ProcessTemplate function

options.MemberAccessStrategy.Register(substitutionObject.GetType());

After adding that line, test with objects (anonymous and typed) passed respecting the IgnoreCase setting option, but test with Dictionary<string, object> with Keys in incorrect case still fail.

apavelm commented 11 months ago

@sebastienros Can you try to pass a dictionary that is initialized with a StringComparison.OrdinalIgnoreCase?

Sorry, what do you mean? Could you please elaborate.

Actually, I would be happy to pass an anonymous object instead of Dictionary, but for now, I can not. I can try to verify templates and data-models manually. But as you know, "manually" is the word I would like to exclude :-) Thanks.

Looking forward to an updated version.

sebastienros commented 11 months ago
var data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
    {
        {"VehicleBrand", "Toyota"},
        {"LicensePlate", "ab123cd11"}
    };
apavelm commented 11 months ago

Oh, a test with StringComparer.OrdinalIgnoreCase passes.

Unfortunately, in a real code, Dictionary is coming from JSON a result of deserialization.

For my case, I think I can tune-up a deserializer in this part. So it is a good bypass option. Thank you, but I would appreciate it if it was the part of library.

Happy holidays!

sebastienros commented 2 months ago

FYI there is a PR that should solve it https://github.com/sebastienros/fluid/pull/681