Skip to content

Is Handlebars.Net case sensitive again? #362

Closed

Description

There are a few places that mention that Handlebars.Net is supposed to be case insensitive as of v1.6.6 but I can't seem to get it to act that way.

Am I doing something wrong in the code? Below is a basic example of what I'm doing

var templateString = "Dear {{displayName}}";
var template = Handlebars.Compile(templateString);

var data = new ExpandoObject();
data.TryAdd("DisplayName", "John");

var output = template(data);

Console.WriteLine(output);

This just results in Dear .
If I change templateString to be "Dear {{DisplayName}}" it would print Dear John

This is using the NuGet package v1.10.1 on a .NET Core 3.1 console application.

Activity

  1. oformaniuk commented on Sep 24, 2020

    @oformaniuk
    Member

    Hello @brandon-vs . Unfortunately not everything can be handled in case-insensitive way. For example consider data object implementing IDictionary<string, object> (applies to your example with ExpandoObject) - in order to get value from the object members of IDictionary<string, object> will be used. If data source provide case-insensitive access to properties (e.g. new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)) you'd get case-insensitive behavior. The same is true for POCOs as the library performs case-insensitive property search.
    Unfortunately the library is not able to handle cases when IDictionary<string, object> data source does not provide case-insensitive behavior as it would require wrapping such object with some kind of proxy that would handle case-insensitive search. Including such functionality as a default behavior can impact performance.

    However starting from 2.0 you would be able to handle such problems on you own by registering custom IObjectDescriptorProvider:

    [Fact]
    public void CaseInsensitiveExpandoObject()
    {
        var templateString = "Dear {{displayName}}";
        var configuration = new HandlebarsConfiguration();
        configuration.CompileTimeConfiguration.ObjectDescriptorProviders.Add(new ExpandoObjectDescriptor());
        var handlebars = Handlebars.Create(configuration);
        var template = handlebars.Compile(templateString);
    
        var data = new ExpandoObject();
        data.TryAdd("DisplayName", "John");
    
        var output = template(data);
    
        Assert.Equal("Dear John", output);
    }
    
    public class ExpandoObjectDescriptor : IObjectDescriptorProvider
    {
        public bool CanHandleType(Type type)
        {
            return typeof(ExpandoObject).IsAssignableFrom(type);
        }
    
        public bool TryGetDescriptor(Type type, out ObjectDescriptor value)
        {
            if (!typeof(ExpandoObject).IsAssignableFrom(type))
            {
                value = ObjectDescriptor.Empty;;
                return false;
            }
            
            value = new ObjectDescriptor(
                type, 
                new ExpandoObjectMemberAccessor(), 
                (descriptor, o) => ((IDynamicMetaObjectProvider)o).GetMetaObject(Expression.Constant(o)).GetDynamicMemberNames()
                );
    
            return true;
        }
        
        private class ExpandoObjectMemberAccessor : IMemberAccessor
        {
            public bool TryGetValue(object instance, Type instanceType, string memberName, out object value)
            {
                if (instance is ExpandoObject expandoObject)
                {
                    var invariant = memberName.ToUpperInvariant();
                    var property = ((IDynamicMetaObjectProvider) expandoObject)
                        .GetMetaObject(Expression.Constant(expandoObject))
                        .GetDynamicMemberNames()
                        .FirstOrDefault(o => o.ToUpperInvariant() == invariant);
    
                    if (property != null)
                    {
                        value = ((IDictionary<string, object>) expandoObject)[property];
                        return true;
                    }
                }
    
                value = null;
                return false;
            }
        }
    }

    Be aware that proposed API is not final and may change prior to v2 release.

  2. ghost closed this as completedon Sep 24, 2020
  3. locked and limited conversation to collaborators on Jan 24, 2022
  4. converted this issue into a discussion #491 on Jan 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions