sebastienros/fluid

MemberAccessStrategy.IgnoreCase doesn't work

Opened this issue · 12 comments

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: "
}

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

Check this

public async Task IgnoreCasing()
{
_parser.TryParse("{{ p.firsTname }}", out var template, out var error);
var options = new TemplateOptions();
options.MemberAccessStrategy.IgnoreCasing = true;
options.MemberAccessStrategy.Register<Person>();
var context = new TemplateContext(options);
context.SetValue("p", new Person { Firstname = "John" });
var result = await template.RenderAsync(context);
Assert.Equal("John", result);
options = new TemplateOptions();
options.MemberAccessStrategy.IgnoreCasing = false;
options.MemberAccessStrategy.Register<Person>();
context = new TemplateContext(options);
context.SetValue("p", new Person { Firstname = "John" });
result = await template.RenderAsync(context);
Assert.Equal("", result);
}

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

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

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

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 :)

@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)

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 ..."
}

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?

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.

@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.

var data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
    {
        {"VehicleBrand", "Toyota"},
        {"LicensePlate", "ab123cd11"}
    };

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!