RazorHelpers

A fluent HTML builder and server-side rendering library for ASP.NET Core Minimal APIs. Build HTML with C# using RenderFragment templates, the Html fluent API, TableBuilder, SelectBuilder, and ComponentHelper.

v1.1.0 .NET 9.0 / 10.0 109 Tests Passing MIT License
# Install via NuGet
$ dotnet add package Noundry.RazorHelpers

Quick Start

using RazorHelpers;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorHelpers(); // Required before Build()
var app = builder.Build();

// Return HTML from a Minimal API endpoint
app.MapGet("/", () =>
{
 var fragment = Html.Div()
 .Class("container")
 .Child(Html.H1("Hello World"))
 .Child(Html.P("Welcome to RazorHelpers"))
 .Render();
 return RazorResults.Razor(fragment);
});

app.Run();

Core Patterns

// Return IResult from a RenderFragment
RazorResults.Razor(fragment)
RazorResults.Razor(fragment, model)

// Render to HTML string
await fragment.RenderAsync(services)
await fragment.RenderAsync(model, services)

// Render component class to string
await ComponentHelper.RenderComponentAsync<T>(services)

// Build HTML fluently
Html.Div().Class("x").Child(Html.P("text")).Render()

// Build tables from collections
Html.Table(items).Column("Name", x => x.Name).Render()

// Build selects from collections
Html.Select(items, "name").Value(x => x.Id).Render()

Html Fluent API

The Html static class provides factory methods for all standard HTML elements.

Structure

Div, Span, P, Section, Article, Header, Footer, Main, Nav, Aside

Headings

H1, H2, H3, H4, H5, H6

Text Formatting

Strong, Em, B, I, U, Code, Pre, Kbd, Mark, Blockquote, and more

Forms

Form, Input, Textarea, Button, Label, Select, Fieldset, Progress, Meter

Links & Media

A, Img, Video, Audio, Iframe, Figure, Picture

Lists

Ul, Ol, Li, Dl, Dt, Dd -- plus collection-based Ul<T>

Fluent Methods

Html.Div()
 .Id("main")
 .Class("container", "mt-4") // Multiple classes
 .ClassIf("active", isActive) // Conditional class
 .Attr("role", "main") // Any attribute
 .Data("id", "123") // data-* attribute
 .Style("color", "red") // Inline style
 .Child(Html.H1("Title")) // Add child element
 .Children(Html.P("A"), Html.P("B")) // Multiple children
 .Render()

Collection Lists

var items = new[] { "Apple", "Banana", "Cherry" };
Html.Ul(items, x => x)
// <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>

// Or with Html.Each for custom elements
Html.Each(products, p =>
 Html.Div().Class("card")
 .Child(Html.H3(p.Name))
 .Child(Html.Span($"${p.Price:F2}")))

TableBuilder API

Build HTML tables from collections with column definitions, row styling, and rich content.

// Column definitions (recommended)
Html.Table(users)
 .Class("table", "table-hover")
 .Column("Name", u => u.Name)
 .Column("Email", u => Html.A($"mailto:{u.Email}", u.Email))
 .Column("Status", u => Html.Span(u.IsActive ? "Active" : "Inactive")
 .Class(u.IsActive ? "text-success" : "text-danger"))
 .RowClass(u => u.IsActive ? "" : "table-secondary")
 .Render()

Manual Table

Html.Table()
 .Class("table", "table-striped")
 .Caption("User List")
 .Header("Name", "Email", "Role")
 .Row("John Doe", "john@example.com", "Admin")
 .Row("Jane Smith", "jane@example.com", "User")
 .Render()

SelectBuilder API

Build HTML select elements from collections with grouping, selected state, and placeholder support.

// From collection
Html.Select(countries, "country")
 .Class("form-select")
 .Placeholder("Select a country...")
 .Value(c => c.Code)
 .Text(c => c.Name)
 .SelectedValue("uk")
 .Render()

// With automatic grouping
Html.Select(cars, "car")
 .Value(c => c.Code)
 .Text(c => c.Name)
 .GroupBy(c => c.Country) // Creates <optgroup>
 .Render()

Manual Select with OptGroups

Html.Select("car")
 .OptGroup("Swedish Cars")
 .Option("volvo", "Volvo")
 .Option("saab", "Saab")
 .EndGroup()
 .OptGroup("German Cars")
 .Option("mercedes", "Mercedes")
 .Option("bmw", "BMW")
 .EndGroup()
 .Render()

RenderFragment Templates

For more complex UI, use RenderFragment delegates with the RenderTreeBuilder API.

public record User(string Name, string Email, int Age);

RenderFragment<User> userCard = user => builder =>
{
 var seq = 0;
 builder.OpenElement(seq++, "div");
 builder.AddAttribute(seq++, "class", "user-card");

 builder.OpenElement(seq++, "h2");
 builder.AddContent(seq++, user.Name); // Auto HTML-encoded
 builder.CloseElement();

 builder.OpenElement(seq++, "p");
 builder.AddContent(seq++, $"Email: {user.Email}");
 builder.CloseElement();

 builder.CloseElement();
};

app.MapGet("/user/{name}", (string name) =>
{
 var user = new User(name, $"{name}@example.com", 30);
 return RazorResults.Razor(userCard, user);
});

ComponentHelper API

Render Razor component classes to HTML strings for use in Minimal API endpoints.

// Without parameters
var html = await ComponentHelper.RenderComponentAsync<GreetingComponent>(services);

// With parameters dictionary
var parameters = new Dictionary<string, object?>
{
 ["Name"] = name,
 ["ShowTime"] = true
};
var html = await ComponentHelper.RenderComponentAsync<GreetingComponent>(services, parameters);

// With single parameter (convenience)
var html = await ComponentHelper.RenderComponentAsync<GreetingComponent, string>(
 services, "Name", name);

Security

Safe (Auto-encoded)

Html.Div(userInput), .Text(userInput), AddContent() -- all auto-encode HTML to prevent XSS.

Dangerous (Raw HTML)

.Raw(content), AddMarkupContent() -- never use with user input. Only for trusted/sanitized content.

Performance Tips

Cache Templates as Static Readonly

Define static readonly RenderFragment<T> fields to avoid creating new delegates per request.

Use Column() Over Row() for Tables

Column definitions are more efficient and auto-generate headers from complex tables.

Builder Instances Are Not Thread-Safe

Create new builder instances per-request. Do not share across threads.

Related Packages