NgSharp 2.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package NgSharp --version 2.0.0
                    
NuGet\Install-Package NgSharp -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NgSharp" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NgSharp" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="NgSharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NgSharp --version 2.0.0
                    
#r "nuget: NgSharp, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NgSharp@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NgSharp&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NgSharp&version=2.0.0
                    
Install as a Cake Tool

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.

NuGet License: MIT

📖 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; targets netstandard2.1 and net8.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 / @for control flow
  • Per-template compile & AST caching (partial evaluation, no codegen)
  • Reflection-free JsonElement path 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
3.0.0 139 7/28/2026
2.0.0 96 7/21/2026
1.0.8 140 4/22/2026
1.0.7 110 4/22/2026
1.0.6 273 8/21/2025
1.0.5 224 8/21/2025
1.0.4 220 8/19/2025
1.0.3 216 8/19/2025
1.0.2 221 8/19/2025
1.0.1 132 8/1/2025

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.