ASTTemplateParser 3.0.4
dotnet add package ASTTemplateParser --version 3.0.4
NuGet\Install-Package ASTTemplateParser -Version 3.0.4
<PackageReference Include="ASTTemplateParser" Version="3.0.4" />
<PackageVersion Include="ASTTemplateParser" Version="3.0.4" />
<PackageReference Include="ASTTemplateParser" />
paket add ASTTemplateParser --version 3.0.4
#r "nuget: ASTTemplateParser, 3.0.4"
#:package ASTTemplateParser@3.0.4
#addin nuget:?package=ASTTemplateParser&version=3.0.4
#tool nuget:?package=ASTTemplateParser&version=3.0.4
AST Template Parser
A blazing-fast, security-hardened template engine for .NET with HTML-like syntax, native JSON support, and 2000x faster cached rendering.
โก Performance at a Glance
| Scenario | Speed | Comparison |
|---|---|---|
| Cached Render | ~0.001ms | ๐ฅ 2000x faster |
| Compiled Expression | ~0.002ms | ๐ 500x faster |
| Data-Aware Cache | ~0.01ms | โก 200x faster |
| Normal Render | ~2ms | Baseline |
โจ Features
Core
- ๐ High Performance - 1,000,000+ cached renders/second
- ๐ Enterprise Security - XSS protection, loop limits, property blocking
- ๐งฉ Component System -
<Element>,<Block>,<Data>,<Nav>components - ๐ Layout System - Master layouts with sections and slots
Caching (v2.0.5)
- โก Render Caching - Cache rendered output for instant response
- ๐ Auto File Invalidation - Cache updates when template file changes
- ๐ Data-Aware Caching - Auto-invalidate when variables change
- โฐ Time-Based Expiration - Optional cache TTL
Template Features
- ๐ฏ Indexers -
{{ items[0] }},{{ dict["key"] }} - ๐งช Filters -
{{ Name | uppercase }},{{ Price | currency }} - ๐ Global Variables - Set once, use everywhere
- ๐ Fragments -
<Define>and<Render>for recursion
Performance Optimizations (v2.0.6)
- ๐งฎ NCalc Expression Caching - Parsed expression trees cached & reused (~2.5x faster)
- โก ICollection Fast Path - O(1) count check instead of enumerator allocation (~10x faster)
- ๐ Adaptive StringBuilder Pool - Tiered small/large pools with template size hints
- ๐ Data Hash Dirty Flag - Skip hash recomputation when variables unchanged (~50x faster)
- ๐๏ธ Pre-allocated Variable Merge - Capacity estimation eliminates dictionary resizing
- ๐ .NET 9.0 & 10.0 Support - Full support for latest .NET frameworks
๐ฅ Native JSON Support (NEW in v3.0.0 - April 12, 2026)
- ๐ Zero-Config JSON Binding โ Pass
JsonConvert.DeserializeObject<dynamic>()directly toSetVariable(). JObject, JArray, and JValue are automatically converted to native .NET types. - โก Upfront Conversion โ JToken โ Dictionary/List conversion happens once at
SetVariable()time, not per-access. Zero reflection overhead during rendering. - ๐ No Hard Dependency โ Newtonsoft.Json types are detected via reflection. If Newtonsoft.Json is not loaded, the engine simply ignores JToken logic with zero cost.
- ๐งฉ Full Property Access โ
{{ item.Title }},{{ item.Properties.Price }},{{ item.Properties['Button Text'] }}all work natively with JSON data. - ๐ ForEach Friendly โ JArray collections work directly in
<ForEach>loops. - ๐ก๏ธ SetGlobalVariable Too โ Same auto-conversion works for global/website-scoped variables.
Master Expression Cache & Ternary Optimization (NEW in v2.2.6 - April 8, 2026)
- ๐ Master Expression Cache โ All template expressions (ternary, null-coalescing, comparisons) are now compiled once and cached. This skips string scanning, security checks, and parsing on every render.
- โก 500% Faster Ternary Operators โ Ternary expressions (
{{ ? : }}) are now ~5x faster, matching the speed of structural@ifblocks. - โ
HTML Attribute Support โ Ternary expressions now work flawlessly inside normal HTML attributes such as
class="{{ cond ? 'a' : 'b' }}". - โ
Numeric Comparisons โ Conditions like
{{ Stock > 0 ? 'Yes' : 'No' }}now evaluate correctly.
Attribute & Expression Fixes (NEW in v2.2.5 - April 8, 2026)
- โ
HTML Attribute Ternary Support โ Ternary expressions now work inside normal HTML attributes such as
class="{{ menuNode.Items ? 'active' : 'unactive' }}". - โ
Mixed Attribute Interpolation โ Mixed values such as
class="menu-link {{ cond ? 'active' : 'inactive' }}"now resolve correctly. - โ
Numeric Ternary Comparisons โ Numeric conditions like
{{ Product.Stock > 0 ? 'In Stock' : 'Out of Stock' }}now evaluate correctly. - โ
Interpolation Ternary Support โ Existing interpolation ternary support remains available, including bracketed paths such as
{{ item['Author'] == 'Rony' ? 'active' : 'inactive' }}. - ๐ฏ Bracketed Expression Handling โ Complex bracketed expressions are no longer misclassified as simple indexer lookups during interpolation.
JsonPath Support (NEW in v2.2.1 - April 4, 2026)
- ๐๏ธ JsonPath Property โ
IncludeInfonow exposes aJsonPathproperty, extracted from thejsonpath="..."attribute on component tags. - ๐ Full Pipeline Support โ Available in
PrepareTemplate(),ExtractIncludeNames(),OnBeforeIncludeRender, andOnAfterIncludeRendercallbacks. - ๐งฉ All Component Types โ Works on
<Include>,<Element>,<Data>,<Nav>, and<Block>tags.
Loop Metadata & Dynamic Attributes (v2.1.0)
- ๐ข Loop Metadata - Use
{{loop.index}},{{loop.count}}, and{{loop.first}}insideForEach - ๐ท๏ธ Attr Filter - Render dynamic attributes only if value exists:
{{ myClass | attr:"class" }} - ๐งน Auto Attribute Cleanup - Enable
RemoveEmptyAttributesinSecurityConfigto auto-strip empty attributes - ๐ ๏ธ Error-Tolerant Parsing -
BlockParsernow preserves invalid block tags as HTML instead of removing them - ๐ Zero-Allocation Metadata - Optimized metadata dictionary reuse for maximum loop performance
- ๐ก๏ธ Component Validation - Blocks
../and:in component paths
BlockParser Dual-Path Support (NEW in v2.1.5 - March 28, 2026)
- ๐งฉ Dual-Path Parsing โ
BlockParsernow uses the same local/global resolution logic as the engine (local pages override). - ๐ Auto .html Extension โ Engine now automatically appends
.htmlinResolveDualPathfor cleaner calls. - ๐ ๏ธ Public Path Helpers โ
ResolvePagePath(name)andResolveComponentPath(name)exposed to TemplateEngine for manual path resolution. - ๐ก๏ธ Validation Overhaul โ
BlockParsernow validates paths against BOTH local and global allowed directories.
Dual-Path Directory Support (NEW in v2.1.4 - March 28, 2026)
- ๐ Local Components Path โ
SetLocalComponentsDirectory()sets a per-engine local override or fallback for components - ๐ Local Pages Path โ
SetLocalPagesDirectory()sets a per-engine local override or fallback for pages - ๐ Priority Control โ
SetDirectoryPriority(preferGlobal):false(default) = local first;true= global first - ๐ก๏ธ Security Aware โ Both paths auto-registered in
AllowedTemplatePathsfor safe file access - ๐ Consistent Resolution โ
LoadComponent(),RenderFile(), andRenderCachedFile()all honour the same dual-path logic
Performance & Security Hardening (v2.1.3 - March 11, 2026)
- ๐ Zero-Allocation ForEach โ Optimized context swap eliminates object allocations in loops.
- โก Ultra-Fast Expressions โ Skip character scans for simple variable lookups (~2-3x faster).
- ๐ก๏ธ Auto-PreWarming โ
PreWarmThemes()andPreWarmAll()to pre-cache templates at startup. - ๐ HTML Auto-Encoding โ Default XSS prevention for all
{{ variable }}outputs. - ๐งฎ Fast Path Resolution โ Zero-allocation resolution for nested paths (e.g.,
item.Name).
Caching & Stability Fixes (NEW in v2.1.2)
- ๐ง Complete Cache Clear โ
ClearCaches()now clears ALL caches (render, path, block, expression, property) - ๐ Path Cache Validation โ Stale cached paths auto-removed when files are moved or deleted
- ๐ Component Change Detection โ
RenderCachedFileauto-detects component file changes via version tracking - ๐งฎ Content-Aware Hashing โ
ComputeDataHashproperly hashes List/Dictionary contents (not references) - โก Race Condition Fix โ Expression cache eviction is now thread-safe under high concurrency
- ๐ ForEach Optimization โ Loop metadata dictionary allocated once and reused across iterations
๐ฆ Installation
# NuGet Package Manager
Install-Package ASTTemplateParser
# .NET CLI
dotnet add package ASTTemplateParser
๐ Quick Start
using ASTTemplateParser;
var engine = new TemplateEngine();
engine.SetPagesDirectory("./pages");
// Set data
engine.SetVariable("User", new { Name = "Alice", Role = "Admin" });
// โก Cached render - 2000x faster on repeat calls!
string html = engine.RenderCachedFile("dashboard.html", "dashboard", includeDataHash: true);
JSON Data (NEW in v3.0.0)
// Read JSON and pass directly โ zero manual conversion needed!
var jsonData = JsonConvert.DeserializeObject<dynamic>(File.ReadAllText("data.json"));
engine.SetVariable("Items", jsonData.Items);
// Template: {{ item.Title }}, {{ item.Properties.Price }}, {{ item.Properties['Button Text'] }}
โก Caching Guide (NEW!)
Static Pages (Fastest)
// Cache indefinitely until file changes
string about = engine.RenderCachedFile("about.html", "about");
string faq = engine.RenderCachedFile("faq.html", "faq");
User-Specific Pages (Smart Cache)
// Auto-invalidate when variables change
engine.SetVariable("User", currentUser);
engine.SetVariable("Cart", cartItems);
string dashboard = engine.RenderCachedFile(
"dashboard.html",
"dashboard",
includeDataHash: true // โฌ
๏ธ Magic! Data changes = new render
);
Time-Based Expiration
// Cache for 5 minutes
string news = engine.RenderCachedFile(
"news.html",
"news",
expiration: TimeSpan.FromMinutes(5)
);
Cache Management
// Invalidate specific cache
TemplateEngine.InvalidateCache("dashboard");
// Invalidate by prefix (e.g., all user caches)
TemplateEngine.InvalidateCacheByPrefix("user-");
// Clear all cache
TemplateEngine.ClearRenderCache();
// Get stats
var stats = TemplateEngine.GetRenderCacheStats();
Console.WriteLine($"Cached pages: {stats["TotalEntries"]}");
๐งฑ Block Parser (NEW in v2.0.8)
Parse Blocks from Page Templates
var engine = new TemplateEngine();
engine.SetPagesDirectory("./pages");
var blockParser = new BlockParser(engine);
// Extract <Block> components from page template
var blocks = blockParser.ParseBlocks("home");
foreach (var block in blocks)
{
Console.WriteLine($"{block.Order}: {block.Name} โ {block.ComponentPath}");
// 0: slider_un โ slider
// 1: about_un โ about/standard
// 2: blog_un โ blog
}
Mixed Content โ Blocks + Raw HTML
// Parse page template that has BOTH <Block> calls AND raw HTML
var segments = blockParser.ParseTemplateSegments("home");
foreach (var segment in segments)
{
if (segment.IsBlock)
{
// Render block via engine
var html = engine.RenderCachedFile(
"block/" + segment.Block.ComponentPath,
"block-" + segment.Block.ComponentPath);
output.AppendLine(html);
}
else if (segment.IsHtml)
{
// Raw HTML โ directly append
output.AppendLine(segment.RawHtml);
}
}
Cache Management
// Clear all block/segment caches
BlockParser.ClearCache();
// Clear specific template cache
BlockParser.ClearCache("home");
// Monitor cache
Console.WriteLine($"Block cache: {BlockParser.CacheCount}");
Console.WriteLine($"Segment cache: {BlockParser.SegmentCacheCount}");
๐ Template Syntax
Variables & Indexers
{{Name}}
{{User.Address.City}}
{{Items[0].Title}}
{{Config["apiKey"]}}
{{ item['Author'] == 'Rony' ? 'active' : 'inactive' }}
Filters
{{ Name | uppercase }}
{{ Price | currency:"en-US" }}
{{ Created | date:"dd MMM yyyy" }}
{{ Description | truncate:100 }}
Conditionals
<If condition="IsLoggedIn">
<p>Welcome, {{User.Name}}!</p>
<ElseIf condition="Role == 'guest'">
<p>Hello, Guest!</p>
<Else>
<p>Please log in</p>
</If>
Loops
<ForEach var="product" in="Products">
<div class="card">
<h3>{{product.Name}}</h3>
<p>{{product.Price | currency}}</p>
</div>
</ForEach>
Components
<Element component="button">
<Param name="text" value="Click Me" />
<Param name="type" value="primary" />
</Element>
<Block component="hero" jsonpath="$.pageData.hero">
<Param name="title" value="{{PageTitle}}" />
</Block>
<Data component="products" name="prod_list" jsonpath="$.api.products">
<Param name="limit" value="10" />
</Data>
๐ Security
var security = new SecurityConfig {
MaxLoopIterations = 500, // DoS protection
MaxRecursionDepth = 5, // Stack protection
HtmlEncodeOutput = true, // XSS protection
BlockedPropertyNames = new HashSet<string> { "Password", "Secret" }
};
var engine = new TemplateEngine(security);
๐ Performance Benchmarks
| Operation | Speed | Notes |
|---|---|---|
| Cache Hit (Static) | ~0.001ms | 1M+ ops/sec |
| Compiled Expression | ~0.002ms | 500K+ ops/sec (5x faster) |
| Cache Hit (Data Hash) | ~0.003ms | 300K+ ops/sec |
| Normal Render (Simple) | ~0.001ms | 886K+ ops/sec |
| Normal Render (ForEach) | ~0.013ms | 75K+ ops/sec |
| Property Access | ~0.00008ms | 12M+ ops/sec |
| JSON SetVariable Overhead | ~0.0001ฮผs | Zero regression |
| ConvertJTokenToNative (passthrough) | ~0.0000ฮผs | Non-JToken = free |
Tested on .NET 8.0+ / Intel i7 / Windows 11
๐ฏ Supported Frameworks
| Framework | Version | Status |
|---|---|---|
| .NET Standard | 2.0 | โ Supported |
| .NET Framework | 4.8 | โ Supported |
| .NET | 6.0 | โ Supported |
| .NET | 8.0 | โ Supported |
| .NET | 9.0 | โ Supported |
| .NET | 10.0 | โ Supported |
๐ Global Variables (App-Wide or Website-Scoped)
Global variables are static variables that persist across ALL TemplateEngine instances. Set them once at application startup, and they're available in every template.
๐ข Website-Scoped Globals (NEW!)
You can now scope global variables to a specific websiteId. This is perfect for multi-tenant applications.
// App-wide global (All websites)
TemplateEngine.SetGlobalVariable("SiteName", "My Main Site");
// Website-scoped global (Only for website ID 5)
TemplateEngine.SetGlobalVariable("ThemeColor", "Blue", websiteId: 5);
TemplateEngine.SetGlobalVariable("Banner", "sale.png", websiteId: 5);
// To use website globals, set the ID on your engine instance
var engine = new TemplateEngine();
engine.SetWebsiteId(5);
// Will use "My Main Site" AND include "ThemeColor" / "Banner"
string html = engine.RenderCachedFile("home.html", "home", includeDataHash: true);
โก Auto Cache Invalidation
Whenever you call SetGlobalVariable(), the engine automatically increments a global version counter. Any Data-Aware Cache (using includeDataHash: true) will detect this change and re-render on the next call to ensure your pages always show the latest global data.
๐ง API Reference
Block Parser Methods
| Method | Description |
|---|---|
ParseBlocks() |
Extract <Block> tags from page template |
ParseTemplateSegments() |
โญ NEW - Parse blocks + raw HTML segments |
ParseBlocksFromContent() |
Parse from string content |
ParseSegmentsFromContent() |
Parse segments from string |
ClearCache() |
Clear all block & segment caches |
CacheCount |
Block cache entry count |
SegmentCacheCount |
Segment cache entry count |
Rendering Methods
| Method | Description |
|---|---|
Render() |
Normal template rendering |
RenderFile() |
Render from file |
RenderCached() |
Cached string template |
RenderCachedFile() |
โญ Recommended - Cached file render |
Cache Methods
| Method | Description |
|---|---|
InvalidateCache(key) |
Remove specific cache |
InvalidateCacheByPrefix(prefix) |
Remove matching caches |
ClearRenderCache() |
Clear all caches |
HasCachedRender(key) |
Check cache exists |
RenderCacheCount |
Get cache count |
GetRenderCacheStats() |
Get detailed stats |
Directory Methods (NEW in v2.1.4 / v2.1.5)
| Method | Description |
|---|---|
SetComponentsDirectory(path) |
Global components directory |
SetPagesDirectory(path) |
Global pages directory |
SetLocalComponentsDirectory(path) |
โญ NEW - Local/override components directory |
SetLocalPagesDirectory(path) |
โญ NEW - Local/override pages directory |
SetDirectoryPriority(preferGlobal) |
โญ NEW - false (default) = local first; true = global first |
ResolvePagePath(name) |
โญ NEW - Resolve page path using dual-path priority |
ResolveComponentPath(name) |
โญ NEW - Resolve component path using dual-path priority |
๐ Dual-Path Directory (NEW in v2.1.4)
Local override (default โ local takes priority)
// Global/theme path for the website
_engine.SetComponentsDirectory(website.GetComponentPath());
_engine.SetPagesDirectory(website.BasePageTemplatePath());
// Local path overrides global (checked first by default)
_engine.SetLocalComponentsDirectory(localThemePath);
_engine.SetLocalPagesDirectory(localPagePath);
// Now: localThemePath checked first โ falls back to website path if not found
string html = _engine.RenderCachedFile("home", "home", includeDataHash: true);
Global priority (global first, local as fallback)
_engine.SetDirectoryPriority(preferGlobal: true);
// Now: website path checked first โ localThemePath used only as fallback
๐ License
MIT License - see LICENSE for details.
๐ Links
Made with โค๏ธ for the .NET community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8
- NCalc.NetCore (>= 1.0.1)
-
.NETStandard 2.0
- NCalc.NetCore (>= 1.0.1)
-
net10.0
- NCalc.NetCore (>= 1.0.1)
-
net6.0
- NCalc.NetCore (>= 1.0.1)
-
net8.0
- NCalc.NetCore (>= 1.0.1)
-
net9.0
- NCalc.NetCore (>= 1.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.4 | 90 | 4/20/2026 |
| 3.0.3 | 81 | 4/18/2026 |
| 3.0.2 | 98 | 4/13/2026 |
| 3.0.1 | 86 | 4/12/2026 |
| 3.0.0 | 95 | 4/12/2026 |
| 2.2.9 | 110 | 4/9/2026 |
| 2.2.8 | 94 | 4/8/2026 |
| 2.2.6 | 93 | 4/8/2026 |
| 2.2.5 | 104 | 4/8/2026 |
| 2.2.4 | 95 | 4/6/2026 |
| 2.2.3 | 99 | 4/6/2026 |
| 2.2.2 | 88 | 4/4/2026 |
| 2.2.1 | 92 | 4/4/2026 |
| 2.2.0 | 103 | 3/29/2026 |
| 2.1.9 | 90 | 3/29/2026 |
| 2.1.6 | 143 | 3/28/2026 |
| 2.1.5 | 138 | 3/28/2026 |
| 2.1.4 | 89 | 3/28/2026 |
| 2.1.3 | 111 | 3/11/2026 |
| 2.1.2 | 99 | 3/10/2026 |
v3.0.4 - (April 20, 2026)
- FIXED: ForEach Filter Support โ Resolved a critical parsing issue where multiple pipes in a <ForEach> tag (e.g., in="Items | take:2") were causing the parser to fail. The parser now correctly identifies the first pipe as a variable separator and treats the remaining part as a filterable expression.
- IMPROVED: Filter Result Resolution โ The engine now automatically converts filtered collections (including LINQ TakeIterator) into concrete lists before looping. This ensures that custom filters using System.Linq work reliably without casting errors or lazy evaluation failures.
- ADDED: Built-in filters "take" and "limit" for easy collection slicing (e.g., Data.Items | take:5).
- PERFORMANCE: Optimized expression scanning priority to ensure filters are detected accurately even when they contain colons or other operators.
v3.0.3 - (April 18, 2026)
- FIXED: Dotted Variable Name Resolution โ Variables with dots in their names (e.g., param["Data.Item"] = item) can now be resolved in nested paths such as {{ Data.Item.Title }}. The engine now correctly handles composite prefix lookups when a direct root match fails.
- IMPROVED: Property-to-Indexer Fallback โ When accessing a property that doesn't exist as a native C# property (common in CMS entities with dynamic fields), the PropertyAccessor now automatically falls back to an indexer (this[name]) before returning null.
- FIXED: Page Template Variable Injection โ In the CMS PageExtension, variables passed to ParseTemplate are now correctly injected into the engine for main page HTML segments, not just for component blocks.
v3.0.2 - (April 13, 2026)
- FIXED: Comparison conditions inside <If> tags (e.g., loop.count == 1, loop.index == 0, Count > 5) were silently failing โ always evaluated to false. Root cause: EvaluateCondition was routing all comparison expressions to NCalc which failed silently. Now uses the engine's own native comparison/logical evaluation path before NCalc fallback.
- ADDED: Native negation support in conditions โ !variable and !path.prop now work correctly in <If> tags.
- ADDED: Native logical operator support โ && and || conditions (e.g., loop.count > 1 && loop.count < 3) now evaluate natively without NCalc.
- PERFORMANCE: Conditions with comparison operators are now faster โ native evaluation bypasses NCalc parsing overhead entirely.
v3.0.1 - (April 12, 2026)
- FIXED: JObject-to-Dictionary conversion was producing empty dictionaries. JObject iterates as JProperty (with .Name/.Value), not KeyValuePair (with .Key/.Value). Now correctly handles both patterns.
v3.0.0 - (April 12, 2026) โ MAJOR RELEASE: Native JSON Support
- ADDED: Native Newtonsoft.Json JToken/JObject/JArray support โ pass DeserializeObject<dynamic>() results directly to SetVariable() with zero manual conversion.
- ADDED: Auto JToken-to-Native conversion in SetVariable() and SetGlobalVariable() โ JObject โ Dictionary<string, object>, JArray โ List<object>, JValue โ .NET primitives.
- ADDED: Reflection-based JToken detection โ NO hard dependency on Newtonsoft.Json. Works only when Newtonsoft.Json is loaded at runtime.
- ADDED: PropertyAccessor.ConvertJTokenToNative() public API for manual conversion when needed.
- ADDED: JToken support in PropertyAccessor.GetValue() and GetIndexerValue() as fallback for unconverted tokens.
- PERFORMANCE: Zero overhead for non-JToken types โ guard clause returns immediately (~0.0000ฮผs per call).
- PERFORMANCE: Upfront conversion at SetVariable time eliminates per-access reflection overhead during rendering (~10x faster than lazy reflection).
- VERIFIED: 886K+ ops/sec (simple) and 75K+ ops/sec (complex) โ no performance regression.
v2.2.9 - (April 9, 2026)
- FIXED: ForEach loops now reliably resolve the current iteration variable from the local loop scope.
- VERIFIED: Example test suite confirms the ForEach Loop scenario renders correctly.
v2.2.8 - (April 8, 2026)
- CLEANUP: Removed unused utility variables and debug logs to eliminate compiler warnings.
- STABILITY: Final refinement of the context-safe expression resolution engine.
v2.2.7 - (April 8, 2026)
- FIXED: Context-Safe Expression Resolution โ Corrected variable lookup in ForEach loops and Nested Components to prioritize the current local scope over the global context.
- IMPROVED: NCalc Parameter Resolution โ Integrated the master expression cache into NCalc's parameter evaluator for faster and more accurate math/logic rendering.
- FIXED: Parameter binding in nested components now correctly resolves interpolation tags ({{ }}) using the parent's current variables stack.
v2.2.6 - (April 8, 2026)
- ADDED: Master Expression Cache โ All template expressions (ternary, null-coalescing, comparisons, etc.) are now compiled into metadata objects and cached.
- PERFORMANCE: 500% faster ternary and null-coalescing operators. Evaluation now skips string scanning, security validation, and parsing on repeat renders.
- PERFORMANCE: Optimized ResolveExpression pipeline to achieve near-structural performance for interpolation tags.
- IMPROVED: Cache management for expressions with a configurable 2000-entry limit.
v2.2.5 - (April 8, 2026)
- FIXED: Ternary expressions now resolve correctly inside normal HTML attributes, including full-value and mixed-value class attributes such as class="{{ cond ? 'a' : 'b' }}" and class="base {{ cond ? 'a' : 'b' }}".
- FIXED: Numeric literal comparisons inside ternary conditions now evaluate correctly (for example {{ Product.Stock > 0 ? 'In Stock' : 'Out of Stock' }}).
v2.2.4 - (April 6, 2026)
- FIXED: Ternary operator and comparison expressions inside interpolation tags now resolve correctly, including bracketed paths such as {{ item['Author'] == 'Rony' ? 'active' : 'inactive' }}.
- IMPROVED: Expression resolution now preserves quoted literal branches and avoids misclassifying complex bracketed expressions during interpolation.
v2.2.3 - (April 6, 2026)
- FIXED: Ternary operator and complex logical conditions inside interpolation tags (e.g., {{ item['Prop'] == 'Val' ? 'a' : 'b' }}). Improved ResolveExpression logic to correctly delegate complex expressions to the NCalc engine instead of incorrectly treating them as simple indexers.
v2.2.2 - (April 4, 2026)
- ADDED: High-performance ReadFile helper in TemplateEngine โ supports reading data files (JSON, TXT, HTML, etc.) from global and local template directories.
- IMPROVED: Smart Caching for ReadFile โ reduces disk I/O by caching raw file content in memory with automatic invalidation on file modification.
- IMPROVED: Generic extension support in ReadFile โ intelligently resolves any file extension beyond just .html.
v2.2.1 - (April 4, 2026)
- ADDED: JsonPath property on IncludeInfo โ extracted from the jsonpath="..." attribute on Include, Element, Data, Nav, and Block tags. Available in PrepareTemplate(), OnBeforeIncludeRender, and OnAfterIncludeRender callbacks.
v2.2.0 - (March 29, 2026)
- UPGRADED: BlockParser attribute extraction is now extremely robust, supporting double-quoted, single-quoted, and unquoted attribute values (e.g., component=about).
- FIXED: Guards in BlockParser now use IsNullOrWhiteSpace() to prevent blocks with empty/whitespace templates from being returned to the CMS, avoiding database validation errors.
v2.1.9 - (March 29, 2026)
- FIXED: ResolveTemplatePath() folder fallback (default.html) was broken for all paths that already had the .html extension appended by ResolveDualPath. When Option 1 (direct .html file) failed, Option 3 tried Directory.Exists on a path ending in ".html" which is always false. Now correctly strips the .html extension before checking for a matching folder/default.html.
v2.1.8 - (March 28, 2026)
- FIXED: RenderBlock() was blindly prepending "block/" to ComponentPath even when it already started with "block/", resulting in "block/block/about" double-prefix paths that caused FileNotFoundException. ComponentPath is now used as-is when it already contains the prefix.
v2.1.7 - (March 28, 2026)
- FIXED: RenderPrepared() was silently ignoring website-scoped globals (_myWebsiteGlobals) โ now uses LayeredVariableLookup identical to Render(), ensuring correct variable priority in multi-tenant CMS.
- FIXED: PreWarmComponents() was parsing each component's AST twice (once for content-hash key, once for component-path key) โ AST is now parsed once and the same entry is reused for both keys.
v2.1.6 - (March 28, 2026)
- FIXED: ResolvePagePath() returned null when only SetLocalPagesDirectory() was configured (global directory was not set).
- FIXED: ResolveComponentPath() returned null when only SetLocalComponentsDirectory() was configured.
- IMPROVED: Both methods now correctly fall back to dual-path resolution as long as at least one directory (local or global) is configured.
v2.1.5 - (March 28, 2026)
- FIXED: BlockParser dual-path support โ BlockParser now uses the same resolution logic as the engine (local pages override).
- FIXED: Auto .html extension in ResolveDualPath โ ensures files are found even when passed without extension (e.g., from CMS).
- ADDED: ResolvePagePath() and ResolveComponentPath() public methods to TemplateEngine for manual path resolution.
- IMPROVED: ValidatePagePath() in BlockParser now correctly validates against both local and global allowed directories.
v2.1.4 - (March 28, 2026)
- ADDED: SetLocalComponentsDirectory() โ per-engine local components path that can override or fall back to the global path.
- ADDED: SetLocalPagesDirectory() โ per-engine local pages path with same dual-path resolution logic.
- ADDED: SetDirectoryPriority(preferGlobal) โ controls lookup order. false (default) = local first; true = global first.
- IMPROVED: LoadComponent(), RenderFile(), and RenderCachedFile() all support priority-aware dual-path resolution.
- IMPROVED: Security checks now validate against both local and global allowed paths.
- IMPROVED: ResolveDualPath() helper centralises path resolution to eliminate code duplication.
v2.1.3 - (March 11, 2026)
- ADDED: Website-Scoped Globals support via SetGlobalVariable(key, value, websiteId).
- FIXED: Global Variable Cache Invalidation โ changing global variables now correctly invalidates data-aware caches.
- PERFORMANCE: Optimized property access and variable resolution.
- STABILITY: Improved security validation and expression evaluation logic.
- IMPROVED: Refined internal template caching and rendering flow.
v2.1.2 - (March 10, 2026)
- PERFORMANCE: Zero-allocation ForEach loops โ in-place context swap eliminates ~400 object allocations per 100-item loop.
- PERFORMANCE: Zero-allocation Fragment rendering โ renders directly into parent StringBuilder, no child Evaluator created.
- PERFORMANCE: Ultra-fast expression path โ simple variables ({{ Name }}) skip character scan entirely (~2-3x faster).
- PERFORMANCE: Fast nested path resolution โ 2-part (item.Name) and 3-part (a.b.c) paths resolve with zero StringBuilder allocation (~3x faster).
- PERFORMANCE: IVariableContext interface with HierarchicalVariableContext for zero-copy variable shadowing in loops and components.
- PERFORMANCE: LayeredVariableLookup implements IVariableContext directly โ eliminates ToMergedDictionary() at render start.
- PERFORMANCE: PropertyAccessor IDictionary lookup optimized โ removed O(N) case-insensitive fallback search.
- PERFORMANCE: NCalc parameter loading uses ToDictionary() only when needed.
- ADDED: PreWarmThemes() โ static method to pre-cache all theme templates at app startup for multi-tenant CMS.
- ADDED: PreWarmComponents() / PreWarmPages() / PreWarmAll() โ instance methods for single-theme pre-warming.
- ADDED: PreWarmDirectory() โ static method for custom directory pre-warming.
- ADDED: Configurable subfolder names in PreWarmThemes() via params string[] subFolders.
- ADDED: CacheStatistics now includes RenderCacheCount and PathCacheCount.
- SECURITY: Auto HTML encoding on all {{ variable }} outputs by default (XSS prevention).
- SECURITY: RawString class and | raw filter for explicit encoding bypass.
- SECURITY: EnableStrictMode for debugging null/missing variables.
v2.1.1 - (March 2, 2026)
- FIXED: ClearCaches() now properly clears ALL caches (render, path, block, segment, expression, property accessor).
- FIXED: Path cache stale data - ResolveTemplatePath validates File.Exists() before returning cached path.
- FIXED: RenderCachedFile now detects component file changes via component version tracking.
- FIXED: ComputeDataHash uses content-aware hashing for collections (List, Dictionary) instead of reference-based.
- FIXED: Expression cache race condition - eviction check moved outside GetOrAdd factory delegate.
- FIXED: Duplicate MaxLoopIterations check removed from ForEach evaluation.
- PERFORMANCE: ForEach loop metadata Dictionary allocated once and reused across iterations.
v2.1.0 - (February 28, 2026)
- ADDED: Loop Metadata - Automatic {{loop.index}}, {{loop.count}}, and {{loop.first}} support in ForEach.
- ADDED: Attr Filter - Cleanly render attributes only when values exist (e.g., {{ class | attr:"class" }}).
- ADDED: Auto-Cleanup - Option to automatically strip empty HTML attributes (RemoveEmptyAttributes in SecurityConfig).
- IMPROVED: Error-Tolerant Block Parsing - Invalid blocks are now preserved as HTML instead of being hidden.
- PERFORMANCE: Optimized loop metadata engine to minimize memory allocations.
v2.0.9 - (February 28, 2026)
v2.0.8 - (February 15, 2026)
- ADDED: Mixed Content Parsing - ParseTemplateSegments() method for page templates with both Block calls and raw HTML.
- ADDED: TemplateSegment class with IsBlock/IsHtml helpers for easy segment type checking.
- ADDED: ParseSegmentsFromContent() for parsing template strings directly.
- ADDED: SegmentCacheCount property for monitoring segment cache usage.
- PERFORMANCE: Pre-compiled static Regex (RegexOptions.Compiled) for block and param parsing (~3-5x faster regex).
- PERFORMANCE: Segment results cached via ConcurrentDictionary (same pattern as ParseBlocks).
- PERFORMANCE: OrdinalIgnoreCase on parameter dictionaries for consistent case-insensitive lookups.
- SECURITY: Path traversal protection - ValidatePath() prevents reading files outside allowed directories.
- SECURITY: Component path validation - blocks ../ and : characters in component attributes.
- IMPROVED: ClearCache() now clears both block and segment caches simultaneously.
v2.0.6 - (February 10, 2026)
- ADDED: .NET 9.0 and .NET 10.0 target framework support.
- PERFORMANCE: NCalc expression caching - parsed LogicalExpression trees are now cached and reused (~2.5x faster repeated expressions).
- PERFORMANCE: Adaptive StringBuilder pooling with tiered small/large pools and template size hints.
- PERFORMANCE: ICollection fast path in IsTruthy - avoids enumerator allocation for List, Array, Dictionary (~10x faster).
- PERFORMANCE: Data hash dirty flag - skips hash recomputation when variables haven't changed (~50x faster cache hits).
- PERFORMANCE: Pre-allocated variable merge with capacity estimation - eliminates dictionary resizing.
- PERFORMANCE: Eliminated .ToString() overhead in data hash computation - uses GetHashCode() directly.
v2.0.5 - (February 9, 2026)
- ADDED: High-Performance Render Template Caching (RenderCachedFile, RenderCached).
- ADDED: Data-aware caching with includeDataHash parameter for auto-invalidation.
- ADDED: Automatic file-based cache invalidation via timestamp detection.
- ADDED: Support for searching both Pages and Components directories in RenderCachedFile.
- ADDED: Optional time-based cache expiration (TimeSpan).
- ADDED: Cache management APIs: InvalidateCache, InvalidateCacheByPrefix, ClearRenderCache.
- ADDED: Cache statistics via GetRenderCacheStats() for monitoring.
- PERFORMANCE: 2000x faster repeated renders with cache hit (~0.001ms vs ~2ms).
v2.0.3 - (January 21, 2026)
- ADDED: Template Filters with Pipe syntax (e.g. {{ Name | uppercase }}).
- ADDED: Built-in filters: uppercase, lowercase, date, currency.
- ADDED: Support for Filter Arguments (e.g. {{ Price | currency:"bn-BD" }}).
- ADDED: Custom Filter Registration via TemplateEngine.RegisterFilter().
- IMPROVED: Filter execution performance with cached delegates.
v2.0.2 - (January 20, 2026)
- FIXED: Resolved "Unsafe expression" errors by making SecurityConfig less restrictive by default.
- FIXED: Improved regex for safe characters in expressions (bracket and quote support).
- IMPROVED: Added detailed reason to TemplateSecurityException for easier debugging.
- IMPROVED: Added support for IList and native Array indexing (e.g. item[0]).
- IMPROVED: Exposed Security property on TemplateEngine for instance-level configuration.
v2.0.1 - (January 20, 2026)
- ADDED: High-performance Indexer Support (item[key]).
- ADDED: Compiled delegate caching for indexers for maximum speed.
- ADDED: Support for dynamic key resolution in templates.
- IMPROVED: Expression evaluation robustness.
- SECURITY: Enhanced indexer validation with BlockedPropertyNames.