NgSharp 2.0.0
See the version list below for details.
dotnet add package NgSharp --version 2.0.0
NuGet\Install-Package NgSharp -Version 2.0.0
<PackageReference Include="NgSharp" Version="2.0.0" />
<PackageVersion Include="NgSharp" Version="2.0.0" />
<PackageReference Include="NgSharp" />
paket add NgSharp --version 2.0.0
#r "nuget: NgSharp, 2.0.0"
#:package NgSharp@2.0.0
#addin nuget:?package=NgSharp&version=2.0.0
#tool nuget:?package=NgSharp&version=2.0.0
NgSharp
An interpreted, Angular-style HTML template engine for .NET — {{ }} interpolation, pipes, directives, server components, and [if]/[for]/[not-empty] + @if/@else/@for control flow, rendering structurally-correct, HTML-escaped output.
Because it interprets templates instead of compiling them to code, NgSharp starts instantly and runs where Razor-based engines can't: Native AOT, trimming, Azure Functions, C# scripts, short-lived processes. Zero third-party dependencies — a purpose-built HTML parser (no AngleSharp), nothing to Roslyn-compile at first use.
📖 Full documentation & live examples →
Why NgSharp
- No runtime code generation — nothing to Roslyn-compile or IL-emit, so its cold start is instant and it stays Native-AOT / trim safe.
- Zero third-party dependencies — only
System.Text.Json; targetsnetstandard2.1andnet8.0. - Angular-style templates — interpolation, pipes,
[attr.x]/[class.x]/[style.x]/[html]bindings, block + attribute control flow,&&/||/ comparisons, ternary. - Extensible — your own pipes, directives and server components.
- Fast & thread-safe — immutable AST + stateless renderer; compile once, render many concurrently.
Install
dotnet add package NgSharp
Quick start
using NgSharp;
var builder = HtmlBuilder.Default;
var html = await builder.BuildFromTemplateAsync(
"<ul><li [for]=\"Users\">{{ Name | upper }}</li></ul>",
new { Users = new[] { new { Name = "ada" }, new { Name = "linus" } } });
// → <ul><li>ADA</li><li>LINUS</li></ul>
Rendering the same template many times? Compile it once — the AST is folded and cached, and it's safe to render concurrently:
var tpl = builder.Compile("<p>Hello, {{ Name }}!</p>");
tpl.Render(new { Name = "Ada" });
tpl.Render(new { Name = "Linus" });
The model can be an object (read via reflection), a System.Text.Json.JsonElement (reflection-free — the AOT / trimming path), or a pre-built NgElement (the hot path). Values are HTML-escaped automatically.
Template syntax
<h1>{{ Title | upper }}</h1>
<p>{{ CreatedAt | date:'yyyy-MM-dd' }} · {{ Price | number:'C0' }} · {{ Views | largeNumber }}</p>
<a [attr.href]="Url" [class.active]="IsCurrent">{{ Label }}</a>
<div [style.color]="Color"></div>
<div [html]="TrustedMarkup"></div>
<span [if]="InStock == true">in stock</span>
<li [for]="Items">{{ Name }}</li>
<ul [not-empty]="Items"> … </ul>
@if (User.Age >= 18) { <b>adult</b> } @else { <b>minor</b> }
@for (Items) { <li>{{ Name }} — {{ Price | number:'C2' }}</li> }
<user-card [name]="User.Name"></user-card>
Expressions support paths, array indices, the computed members .Count / .Length, comparisons == != < > <= >=, && / ||, ternary and pipes. Truthiness is strict — only a real boolean is truthy.
Built-in pipes: date, number, largeNumber, upper, image.
Extend it
Three interfaces — implement one, register it on a builder, use it in templates.
// Pipe: {{ value | lower }}
public sealed class LowerPipe : IPipe
{
public string PipeName => "lower";
public string Transform(string tagName, NgElement value, string argument)
=> value.GetString()?.ToLowerInvariant();
}
builder.RegisterPipe<LowerPipe>();
// Directive: [hidden]="expr" — mutate the host element
public sealed class HiddenDirective : IDirective
{
public string DirectiveName => "hidden";
public void Apply(DirectiveElement element, NgElement content)
{
if (content.GetBoolean() == true) element.SetAttribute("hidden", "");
}
}
builder.RegisterDirective<HiddenDirective>();
// Component: <badge [count]="Total"></badge>
public sealed class Badge : IComponent
{
public string ComponentName => "badge";
public int Count { get; set; } // bound from the [count] attribute
public string Render() => $"<span class=\"badge\">{Count}</span>";
}
builder.RegisterComponent<Badge>();
Pipes and directives are plain interface calls (reflection-free); component property binding uses reflection, so preserve those members under trimming / Native AOT.
Performance
NgSharp interprets — it never compiles to code, so it wins decisively on cold start and stays AOT-safe, while remaining competitive warm. Rendering a 96-item product catalogue across six .NET engines, byte-identical output (Apple M1 Max, .NET 10):
| Engine | Cold (first render) | Warm (steady state) |
|---|---|---|
| NgSharp | ~78 µs | ~47 µs · ~26 µs with a reused context |
| RazorLight | ~28,000 µs (Roslyn compile) | ~37 µs |
| Handlebars.Net | ~4,900 µs (codegen) | ~50 µs |
| Fluid | ~57 µs | ~48 µs |
| Scriban | ~176 µs | ~155 µs |
Cold, NgSharp is tens to hundreds of times faster than the engines that compile to code — exactly the tax that hurts in serverless, Native AOT and short-lived processes. The full benchmarks include a feature-complete document rendered byte-identically by NgSharp, Handlebars and RazorLight.
When to use it
Reach for NgSharp when you need HTML-correct, escaped output with a compile-free cold start and zero dependencies — Native AOT, trimming, serverless, C# scripts, short-lived processes, or generating PDFs, emails and server-side HTML. For a full MVC view engine, use Razor; for maximum warm-loop throughput on long-lived servers, the compiled engines edge it.
Roadmap
- Pipes, directives and server components
-
[if]/[for]/[not-empty]+@if/@else/@forcontrol flow - Per-template compile & AST caching (partial evaluation, no codegen)
- Reflection-free
JsonElementpath for Native AOT / trimming - Zero third-party dependencies (AngleSharp removed)
- NuGet publication
- Reusable template fragments (
ng-template-style)
Contributing
Pull requests are welcome — build and share your own pipes, directives or components.
License
MIT — free to use and modify.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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 was computed. 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 | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- System.Text.Json (>= 9.0.7)
-
net8.0
- System.Text.Json (>= 9.0.7)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NgSharp:
| Package | Downloads |
|---|---|
|
NgSharp.Map
Static Google Maps component for NgSharp — renders map markers with SkiaSharp. Optional add-on so the NgSharp core stays free of the native SkiaSharp dependency. |
GitHub repositories
This package is not used by any popular GitHub repositories.
2.0: rewritten as an immutable AST + stateless renderer. Zero third-party dependencies — AngleSharp replaced by a purpose-built HTML template parser (analyzer-verified trim/Native-AOT-clean core; ~3x faster cold-start). @if/@else/@else if/@for control-flow, && / || operators, per-template AST cache (HtmlBuilder.Compile), NgElement.FromObject fast path. BREAKING: IPipe.Transform/IComponent.Render/IDirective.Apply no longer take an AngleSharp IElement (pipes take a tag-name string, components return their HTML, directives mutate a DirectiveElement); the map component moved to the separate NgSharp.Map package; NgElement.Children/Properties are now IReadOnlyList/IReadOnlyDictionary.