Skip to content

Bump Handlebars.Net from 2.1.6 to 2.4.3 - #287

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/src/StackAlchemist.Engine/Handlebars.Net-2.4.3
Open

Bump Handlebars.Net from 2.1.6 to 2.4.3#287
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/src/StackAlchemist.Engine/Handlebars.Net-2.4.3

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 10, 2026

Copy link
Copy Markdown
Contributor

Updated Handlebars.Net from 2.1.6 to 2.4.3.

Release notes

Sourced from Handlebars.Net's releases.

2.4.3

A performance-focused release. Combined with the rendering work already shipped in 2.4.0, common rendering paths are now up to 56% faster than pre-2.4.0, and template compilation is up to 88% faster (#​668). No API or behavior changes; all improvements are behavior-preserving and were validated against the full test suite (1912 tests) and A/B benchmark runs at every step.

Performance

Rendering — up to 56% faster since pre-2.4.0

Suite Case Pre-2.4.0 2.4.3 Δ (cumulative)
RenderList N=100, object 27.48 µs 12.16 µs −56%
RenderSimple object 856.85 ns 431 ns −50%
RenderNested rows=20, object 24.52 µs 12.44 µs −49%
RenderToString clean 13.95 µs 7.48 µs −46%

Methodology: the pre-2.4.0 baseline is the "Before" figure from each 2.4.0 change's own A/B benchmark (#​652, #​651); the 2.4.3 figure is this release's measured result. 2.4.1 and 2.4.2 shipped no rendering changes, so the chain is valid — confirmed by 2.4.0's "After" and 2.4.3's own "2.4.2" baseline landing within noise of each other on the overlapping cases.

A few cases were only benchmarked on one side of that chain and can't be combined:

Suite Case Baseline Result Δ Baseline release
RenderToString html 13.15 µs 10.37 µs −21% 2.4.2 (no pre-2.4.0 figure)
RenderList N=1000, dictionary 191.3 µs 126.7 µs −34% 2.4.2 (no pre-2.4.0 figure)
EndToEnd 26.1 µs 23.5 µs −10% 2.4.2 (no pre-2.4.0 figure)
RenderSimple dictionary 509.96 ns 399.20 ns −22% pre-2.4.0 (from 2.4.0's #​651; not re-benchmarked in 2.4.3)

Allocation reductions were only measured within this release (2.4.0's notes reported allocation improvements qualitatively, not in bytes, so they aren't chainable):

Suite Case 2.4.2 2.4.3 Δ
RenderToString clean 30.2 KB 13.4 KB −56%
RenderToString html 33.4 KB 16.7 KB −50%

What changed:

Since pre-2.4.0 (#​652, #​651, #​653):

  • ObjectDescriptor's member accessor is pre-bound to its described type instead of re-resolving through a shared type-keyed lookup on every access, and bool property reads return cached boxed instances instead of allocating a fresh box per read. (#​652)
  • HTML encoders bulk-write clean runs of text via SearchValues<char> (net8.0+) or a plain scan (netstandard) instead of one TextWriter.Write(char) call per character, falling back to the original per-character path only where escaping is actually needed. (#​651)
  • The boxed-integer cache used for {{#each}} iterator indexes grew from 20 to 1024 entries, eliminating a 24-byte-per-item allocation that was the dominant remaining allocation source in list rendering (e.g. 23.5 KB → 0 B for a 1000-item {{#each}}). (#​653)

In this release (#​667):

  • Cache helper-resolver presence in the late-bind descriptors. ObservableList<T>.Count acquires a ReaderWriterLockSlim per call, and every simple {{name}} paid it once per render — loops paid hundreds of lock acquisitions per render. The descriptors now subscribe once to the append-only resolver list and keep a flag; resolvers registered after compile still take effect.
  • Retain up to 32K chars in the pooled ReusableStringWriter. Outputs over 4096 chars discarded the pooled writer every render, re-growing a fresh StringBuilder(16) chunk by chunk — most of RenderToString's allocations.
  • Monomorphic descriptor cache on ChainSegment. Dotted member access re-resolved the instance's ObjectDescriptor through the ambient context + type-keyed lookup on every segment per render; each segment now holds an immutable (factory, version, type) → descriptor entry, self-invalidated via a version stamp when descriptor providers are registered.
  • Skip the frame-helper cascade walk when no frame-local helpers exist (decorator / in-render registration is tracked per frame).
  • Skip the ConditionalWeakTable probe in SafeStrings until a safe-marked string is ever produced.
  • Read ThrowOnUnresolvedBindingExpression only on the unresolved branch of path resolution.
  • Cheaper falsy checks: typed zero comparisons instead of Convert.ToBoolean dispatch, an O(1) ICollection.Count emptiness fast path, and enumerator disposal in Any().

Compilation — up to 88% faster (#​668)

| Benchmark | 2.4.2 | 2.4.3 | Change |
... (truncated)

2.4.2

Compatibility notes

  • The published package now includes a net10.0 target. CI was already building and testing against the .NET 10 SDK, and the test/benchmark projects already targeted net10.0, but the Handlebars library itself only shipped netstandard2.0, netstandard2.1, and net8.0 — so .NET 10 apps silently fell back to the net8.0 binary. The package now ships a first-class net10.0 build alongside the existing targets. (#​666, fixes #​659)
  • No API or behavior changes. Existing targets are unchanged; consumers on .NET 10 simply pick up the new target automatically on upgrade.

Contributors

@​rexm

Full Changelog: Handlebars-Net/Handlebars.Net@2.4.1...2.4.2

2.4.1

Fixes

Both regressions below were introduced on 2026-06-20 and shipped in 2.4.0 (released 2026-08-06). Neither was an intentional public API change — this release restores prior correct behavior.

  • Static template text no longer has its line endings silently rewritten. A fix for internal indentation handling started normalizing every \r\n/\r in static template text to \n, so a literal \r\n a caller wrote into a template string (e.g. between {{#each}} iterations) was silently turned into \n. Static text now round-trips verbatim, matching this library's long-standing behavior and handlebars.js. (#​663, fixes #​661)
  • Subexpression results are plain strings again, not an internal wrapper type. A fix for double-encoding across subexpression boundaries (#​543) wrapped every writer-based helper's captured output in an internal sealed class SafeString when used as a subexpression argument. Only a few internal call sites knew how to unwrap it — any other consumer, including reflection-based/typed helper binders in third-party packages (e.g. Handlebars.Net.Helpers), received an opaque type it could neither cast to string nor unwrap, throwing InvalidCastException. The double-encoding fix is preserved, but the signal is now carried by an invisible reference-keyed marker instead of a boxing type, so the value is a genuine System.String everywhere except the one place that needs to know. (#​664, fixes #​660)

Compatibility notes

  • No API surface changes. Both fixes are behavior reverts to what 2.3.0 and earlier already did; anything that worked before 2.4.0 works the same way again.
  • If your code adapted to either regression (e.g. expected \n-only output regardless of source line endings, or handled a SafeString-typed subexpression argument), that adaptation is no longer necessary but should remain harmless.

Contributors

@​rexm

Full Changelog: Handlebars-Net/Handlebars.Net@2.4.0...2.4.1

2.4.0

Performance

Rendering plain .NET objects and string-heavy templates got substantially faster this release, across three targeted changes to the hot rendering path:

  • ~10–33% faster object rendering, less allocationObjectDescriptor's member accessor is now pre-bound to its described type instead of re-resolving through a shared type-keyed lookup on every access, and bool property reads return cached boxed instances instead of allocating a fresh box per read. (#​652)
    Benchmark Case Before After Δ
    RenderSimple object 856.85 ns 575.79 ns −32.8%
    RenderNested object, rows=20 24.52 us 18.94 us −22.7%
    RenderList N=100, object 27.48 us 22.77 us −17.1%
  • ~10–20% faster string-heavy rendering — HTML encoders now bulk-write clean runs of text via SearchValues<char> (net8.0+) or a plain scan (netstandard) instead of one TextWriter.Write(char) call per character, falling back to the original per-character path only where escaping is actually needed. Output is byte-for-byte identical. (#​651)
    Benchmark Case Before After Δ
    RenderSimple dictionary 509.96 ns 399.20 ns −21.7%
    RenderList N=100, object 27.48 us 22.05 us −19.8%
    RenderToString clean 13.95 us 10.97 us −21.4%
  • Zero-allocation {{#each}} iteration — the boxed-integer cache used for iterator indexes grew from 20 to 1024 entries, eliminating a 24-byte-per-item allocation that was the dominant remaining allocation source in list rendering (e.g. 23.5 KB → 0 B for a 1000-item {{#each}}). (#​653)

Combined, typical object-rendering and list-rendering templates should see meaningfully lower latency and near-zero allocation on the common paths; dictionary/expando-backed templates benefit from the encoder work but were otherwise already efficient.

New features

  • Nullable Reference Types — the entire public API surface is now annotated for Nullable Reference Types. Binary- and runtime-compatible (annotations are compile-time metadata only); projects without <Nullable>enable</Nullable> are unaffected. Nullable-enabled consumers get compiler-checked null contracts on the public API, and extensibility interfaces (IPartialTemplateResolver, ITextEncoder, IMemberAccessor, IHelperResolver, IFormatterProvider, IObjectDescriptorProvider, IHelperDescriptor<T>, ViewEngineFileSystem) gained nullability annotations that may surface mismatch warnings (e.g. CS8767) in existing implementations until updated. (#​642, @​TheConstructor)
  • System.Text.Json.JsonElement support — first-class support for JsonElement (e.g. the result of JsonSerializer.Deserialize<object>(json)) in templates: nested member access, {{#each}} iteration over both JSON objects and arrays, and correct {{#if}}/{{#unless}} truthiness — bringing it to parity with the existing Newtonsoft JObject/JToken support. (#​657)
  • Multi-dimensional array support — true C# multi-dimensional arrays (e.g. int[,]) can now be indexed via path expressions ({{grid.[0].[1]}}) and iterated with {{#each}}, which walks the outer-most dimension and yields row/slab slices for the rest. Jagged arrays and existing IList/IEnumerable behavior are unaffected. (#​649)
  • else if chaining for block helpers{{else name args}}...{{/outer}} now works for any block helper, not just {{#if}}, and chains recursively to any depth, e.g. {{#StringEqualityBlockHelper @​value 'dog'}}...{{else StringEqualityBlockHelper @​value 'cat'}}...{{else}}...{{/StringEqualityBlockHelper}}. (#​648)

Fixes

  • Properties whose only implementation is a C# 8+ default interface member (declared and bodied on an interface, not overridden by the concrete class) are now resolved correctly by both {{PropertyName}} lookup and {{#each this}} enumeration, instead of being silently skipped. (#​658, fixes #​601)
  • {{#*inline "name" ...}} no longer throws when passed hash arguments or extra positional arguments, matching Handlebars.js's inline decorator behavior. (#​647, fixes #​560)
  • Corrected the nullable annotation on Try* out-parameters for concrete reference types (introduced in #​642) from [MaybeNullWhen(false)] out T to [NotNullWhen(true)] out T?, matching BCL convention (e.g. Uri.TryCreate) and giving a stronger compiler guarantee against unchecked dereferences. Affects ~20 Try* methods across IObjectDescriptorProvider/ObjectDescriptor, IFormatterProvider, DynamicMemberAccessor, TypeExtensions, BindingContext, PathResolver, and BlockAccumulatorContext. Compile-time-only change, not binary breaking. (#​655, fixes #​654)
  • Resolved a SonarCloud reliability regression (A→B) surfaced by #​642's diff against two pre-existing mutable-array-exposure smells; Closure.A is now internal and PathInfo.Segments carries an explicit suppression. (#​656)

Compatibility notes

  • All changes in this release are binary-compatible. The Nullable Reference Types annotations and the NotNullWhen/MaybeNullWhen correction are compile-time metadata only.
  • If you implement IPartialTemplateResolver, ITextEncoder, IMemberAccessor, IHelperResolver, IFormatterProvider, IObjectDescriptorProvider, IHelperDescriptor<T>, or derive from ViewEngineFileSystem, and build with <Nullable>enable</Nullable>, you may see new nullability-mismatch warnings until your implementation's annotations are updated to match.
  • HandlebarsConfiguration.FileSystem is now declared nullable (ViewEngineFileSystem?), matching its actual default.
  • Built-in collection formatters now throw ArgumentNullException (with parameter name) instead of a raw NullReferenceException for null/mismatched values.

Contributors

@​TheConstructor, @​rexm

Full Changelog: Handlebars-Net/Handlebars.Net@2.2.0...2.4.0

2.3.0

Changes

  • Nullable Reference Types @​TheConstructor (#​642)

    The entire public API surface is now annotated for Nullable Reference Types.

    Compatibility notes:

    • Binary- and runtime-compatible. Nullability annotations are compile-time metadata only; generated IL is unchanged. Projects without <Nullable>enable</Nullable> are unaffected.
    • Nullable-enabled consumers get compiler-checked null contracts on the public API: Try* methods are annotated with [MaybeNullWhen(false)], optional parameters and nullable returns are declared with ?, etc.
    • If you implement extensibility interfaces, their signatures gained nullability annotations and your existing implementations may produce nullability-mismatch warnings (e.g. CS8767) until you add the matching ? annotations. Affected: IPartialTemplateResolver, ITextEncoder, IMemberAccessor, IHelperResolver, IFormatterProvider, IObjectDescriptorProvider, IHelperDescriptor<T>, and the ViewEngineFileSystem base class.
    • HandlebarsConfiguration.FileSystem is now declared nullable (ViewEngineFileSystem?), matching its actual default.
    • The built-in collection formatters now throw ArgumentNullException with a parameter name instead of a raw NullReferenceException when given a null or mismatched value.

Contributors

@​TheConstructor

Full Changelog: Handlebars-Net/Handlebars.Net@2.2.0...2.3.0

2.2.0

Handlebars.Net 2.2.0

⚠️ Behavior & compatibility changes — please read before upgrading

  • Dropped end-of-life target frameworks. Removed netstandard1.3, net451, and net6. The package now targets netstandard2.0, netstandard2.1, and net8.0. Modern consumers (.NET Framework 4.6.1+, .NET Core 2.0+, .NET 5/8+) are unaffected via netstandard2.0; only consumers on the removed legacy frameworks need to stay on 2.1.x.
  • Single quote (') is now HTML-encoded by default (#​546), matching Handlebars.js behavior. This is invisible when rendering HTML, but changes raw output bytes — review any exact-string or snapshot assertions on rendered output.

Bug fixes

  • Case-sensitive resolution of same-spelling path variables, resolved independently in PathBinder (#​434)
  • Case-sensitive key lookup for IDictionary/Hashtable (#​521) and DictionaryMemberAccessor (#​466)
  • @​partial-block now usable inside #if and as a block partial (#​519)
  • #if with includeZero=true now supported; includeZero honored in built-in conditional blocks (#​285)
  • Parent context traversal inside custom block helpers within #each (#​539)
  • Safe-string flag preserved when helper output crosses a subexpression boundary (#​543)
  • WriteSafeString encoding consistent regardless of helper registration order (#​559)
  • Preserve backslashes in template literal text (#​462) and double backslashes in static text (#​349)
  • Preserve partial indentation (#​614)
  • Handle escaped double-quotes in delimited string literal arguments (#​584)
  • Allow Unicode letters as first character in identifiers (#​416); strip invisible Unicode chars (BOM, etc.) from identifiers (#​605)
  • Clearer error when a partial is registered on the wrong Handlebars instance (#​545)
  • Removed byref/in delegate parameters incompatible with Mono/Xamarin (#​458)
  • Prevent unbounded DictionarySlim growth when replacing existing keys (#​541)
  • Normalize CRLF to LF in StaticConverter for platform-independent output

Maintenance

  • CI moved to windows-latest; bumped deprecated GitHub Actions and SDK versions; pinned third-party actions to commit SHAs
  • Resolved SonarCloud security/quality findings; dev environment updated to .NET 10
  • Added render-time/compile-scaling benchmark suite and Handlebars.js regression coverage

Commits viewable in compare view.

@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Aug 10, 2026
@dependabot
dependabot Bot requested a review from stevenfackley as a code owner August 10, 2026 11:46
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code labels Aug 10, 2026
@stevenfackley

Copy link
Copy Markdown
Owner

@dependabot rebase

---
updated-dependencies:
- dependency-name: Handlebars.Net
  dependency-version: 2.4.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot
dependabot Bot force-pushed the dependabot/nuget/src/StackAlchemist.Engine/Handlebars.Net-2.4.3 branch from 6a0166a to 4aa29b0 Compare August 15, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant