From 01fc1e0956839d03626c681a71d43043265e878e Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Sat, 20 Jun 2026 13:53:13 -0400 Subject: [PATCH] fix: case-sensitive key lookup for IDictionary/Hashtable (issue #521) The DictionaryMemberAccessor was using ChainSegment.LowerInvariant to index into a non-generic IDictionary (e.g. Hashtable), silently downcasing the lookup key. This meant uppercase keys like "NAME" could never be found via {{NAME}}, while lowercase keys resolved incorrectly for any casing in the template. The bug was introduced in commit 9d52004 when the string memberName parameter was replaced with ChainSegment: LowerInvariant was chosen instead of TrimmedValue. The fix restores the original exact-match behaviour, which matches Handlebars.js (JS object keys are always case-sensitive). Fixes #521 Co-Authored-By: Claude Sonnet 4.6 --- source/Handlebars.Test/IssueTests.cs | 36 +++++++++++++++++++ .../DictionaryMemberAccessor.cs | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index 31136117..da1511d3 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -733,5 +733,41 @@ public void UnrecognisedExpressionThrowsOutOfMemoryException() Assert.Throws(()=> Handlebars.Compile(source)); } + + // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/521 + // Hashtable with an uppercase key should be accessible using the same-cased expression. + // The IDictionary accessor was incorrectly lowercasing the lookup key. + [Fact] + public void HashtableUppercaseKeyResolvesWithMatchingExpression() + { + var template = Handlebars.Compile("Hello {{NAME}}"); + var result = template(new Hashtable { { "NAME", "alice" } }); + Assert.Equal("Hello alice", result); + } + + [Fact] + public void HashtableLowercaseKeyStillResolves() + { + var template = Handlebars.Compile("Hello {{name}}"); + var result = template(new Hashtable { { "name", "bob" } }); + Assert.Equal("Hello bob", result); + } + + [Fact] + public void GenericDictionaryUppercaseKeyUnchanged() + { + var template = Handlebars.Compile("Hello {{NAME}}"); + var result = template(new Dictionary { { "NAME", "charlie" } }); + Assert.Equal("Hello charlie", result); + } + + [Fact] + public void HashtableLookupIsCaseSensitive() + { + // {{name}} should NOT resolve a key "NAME" — JS objects are case-sensitive + var template = Handlebars.Compile("Hello {{name}}"); + var result = template(new Hashtable { { "NAME", "dave" } }); + Assert.Equal("Hello ", result); + } } } \ No newline at end of file diff --git a/source/Handlebars/MemberAccessors/DictionaryMemberAccessor.cs b/source/Handlebars/MemberAccessors/DictionaryMemberAccessor.cs index 6255d6df..710f2754 100644 --- a/source/Handlebars/MemberAccessors/DictionaryMemberAccessor.cs +++ b/source/Handlebars/MemberAccessors/DictionaryMemberAccessor.cs @@ -12,7 +12,7 @@ public bool TryGetValue(object instance, ChainSegment memberName, out object val // Only string keys supported - indexer takes an object, but no nice // way to check if the hashtable check if it should be a different type. var dictionary = (IDictionary) instance; - value = dictionary[memberName.LowerInvariant]; + value = dictionary[memberName.TrimmedValue]; return true; } }