Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MemberAccessStrategy.IgnoreCase doesn't work #615

Open
apavelm opened this issue Dec 22, 2023 · 13 comments
Open

MemberAccessStrategy.IgnoreCase doesn't work #615

apavelm opened this issue Dec 22, 2023 · 13 comments
Labels

Comments

@apavelm
Copy link

apavelm commented Dec 22, 2023

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
Copy link
Author

apavelm commented Dec 22, 2023

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

@hishamco
Copy link
Collaborator

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);
}

@sebastienros
Copy link
Owner

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

@sebastienros
Copy link
Owner

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

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

@hishamco
Copy link
Collaborator

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
Copy link
Author

apavelm commented Dec 23, 2023

@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
Copy link
Author

apavelm commented Dec 23, 2023

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
Copy link
Owner

sebastienros commented Dec 23, 2023

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
Copy link
Author

apavelm commented Dec 23, 2023

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
Copy link
Author

apavelm commented Dec 23, 2023

@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
Copy link
Owner

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

@apavelm
Copy link
Author

apavelm commented Dec 24, 2023

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
Copy link
Owner

FYI there is a PR that should solve it #681

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants