HeimdallFramework.Server
2.0.1
dotnet add package HeimdallFramework.Server --version 2.0.1
NuGet\Install-Package HeimdallFramework.Server -Version 2.0.1
<PackageReference Include="HeimdallFramework.Server" Version="2.0.1" />
<PackageVersion Include="HeimdallFramework.Server" Version="2.0.1" />
<PackageReference Include="HeimdallFramework.Server" />
paket add HeimdallFramework.Server --version 2.0.1
#r "nuget: HeimdallFramework.Server, 2.0.1"
#:package HeimdallFramework.Server@2.0.1
#addin nuget:?package=HeimdallFramework.Server&version=2.0.1
#tool nuget:?package=HeimdallFramework.Server&version=2.0.1
HeimdallFramework.Server
HeimdallFramework.Server provides the ASP.NET Core server runtime for the Heimdall framework.
It exposes middleware, endpoints, and page primitives that enable HTML-first applications, where the server produces documents and UI updates using hypermedia exchange instead of client-side rendering frameworks.
Most applications will use both packages:
- HeimdallFramework.Server → server runtime
- HeimdallFramework.Web → client runtime static assets
Install
dotnet add package HeimdallFramework.Server --prerelease
(Optional client runtime)
dotnet add package HeimdallFramework.Web --prerelease
Minimal setup
Heimdall requires ASP.NET Core antiforgery.
using Heimdall.Server;
var builder = WebApplication.CreateBuilder(args);
// Required (Heimdall uses same-origin + antiforgery for actions/SSE)
builder.Services.AddAntiforgery();
// Register Heimdall services
builder.Services.AddHeimdall(options =>
{
options.EnableDetailedErrors = true; // optional
});
var app = builder.Build();
// Required
app.UseAntiforgery();
// Static assets (needed if using HeimdallFramework.Web)
app.MapStaticAssets();
app.UseStaticFiles();
// Heimdall middleware + endpoints (v1 routes under /__heimdall)
app.UseHeimdall();
app.Run();
Pages (core primitive)
In Heimdall, a page is a function that returns HTML.
Routes map directly to rendering functions — no view engine, no template requirement, no SPA hydration step.
app.MapHeimdallPage("/", ctx =>
{
return Html.Tag("main",
Html.Tag("h1", "Hello Heimdall"),
Html.Tag("p", "The browser requested a document. The server produced it.")
);
});
Async example:
app.MapHeimdallPage("/dashboard", async (sp, ctx) =>
{
var repo = sp.GetRequiredService<IDashboardRepository>();
var data = await repo.Get();
return DashboardPage.Render(data);
});
Layouts and composition
Heimdall does not impose a layout system.
Layouts are normal functions that wrap page content.
app.MapHeimdallPage("/", ctx =>
{
var page = HomePage.Render(ctx);
return MainLayout.Render(page);
});
This keeps composition explicit, strongly-typed, and server-native.
Content actions (server UI updates)
Heimdall supports server actions that return HTML fragments for DOM updates.
Client triggers → server executes → server returns HTML.
- No JSON DTO layer required
- No client rendering layer required
- HTML is the contract
Typical flow:
User interaction → Heimdall client runtime → POST content action → server returns HTML → DOM swap
Responses may include <invocation> directives for out-of-band updates.
Content actions can be static functions or instance methods. Static actions are the smallest primitive:
[ContentInvocation("cart.clear")]
public static IHtmlContent ClearCart(HttpContext ctx)
{
return CartSummary.Empty();
}
For MVC-style applications, prefer action classes with constructor DI. Heimdall creates the action type from request services using ASP.NET Core DI. If the action type itself is registered, Heimdall uses that registration; otherwise it activates the type with ActivatorUtilities.
public sealed class OrderActions(IOrderRepository orders)
{
[ContentInvocation("orders.filter")]
public async Task<IHtmlContent> Filter(
[ContentPayload] OrderFilter filter,
CancellationToken ct)
{
var results = await orders.SearchAsync(filter, ct);
return OrderList.Render(results);
}
}
Use [ContentInvocationPrefix] to namespace an action class without repeating the full id on every method:
[ContentInvocationPrefix("orders")]
public sealed class OrderActions(IOrderRepository orders)
{
[ContentInvocation("filter")]
public async Task<IHtmlContent> Filter(OrderFilter filter, CancellationToken ct)
{
var results = await orders.SearchAsync(filter, ct);
return OrderList.Render(results);
}
[ContentInvocation]
public IHtmlContent Summary()
{
return OrderSummary.Render(orders.GetSummary());
}
}
The resolved invocation ids are orders.filter and orders.Summary. Invocation ids are still globally unique after prefixing.
Content actions honor ASP.NET Core request timeout metadata:
using Microsoft.AspNetCore.Http.Timeouts;
[ContentInvocation("search")]
[RequestTimeout(milliseconds: 2000)]
public static async Task<IHtmlContent> Search(SearchPayload payload, CancellationToken ct)
{
var results = await SearchService.QueryAsync(payload.Query, ct);
return SearchResults.Render(results);
}
Named timeout policies can be reused with [RequestTimeout("PolicyName")] after configuring ASP.NET Core request timeouts with AddRequestTimeouts(...). [DisableRequestTimeout] can be applied to a content action or action class to opt out of a default timeout.
Content actions also honor ASP.NET Core authorization metadata:
using Microsoft.AspNetCore.Authorization;
[Authorize(Roles = "Admin")]
[ContentInvocation("admin.refresh")]
public static IHtmlContent RefreshAdminPanel(HttpContext ctx)
{
return AdminPanel.Render(ctx.User);
}
[Authorize] can be applied to an action method or containing action class. [AllowAnonymous] can be used to opt out at either level. Heimdall uses the registered ASP.NET Core authorization services, policies, authentication schemes, and challenge/forbid handlers.
Content action parameters are classified without constructing services. Heimdall uses ASP.NET Core's IServiceProviderIsService for implicit service detection and supports explicit markers when a type is ambiguous:
using Microsoft.AspNetCore.Mvc;
[ContentInvocation("orders.filter")]
public static IHtmlContent FilterOrders(
[ContentPayload] OrderFilter filter,
[FromServices] IOrderRepository orders)
{
return OrderList.Render(orders.Search(filter));
}
[ContentPayload] marks the single payload parameter. [FromServices] forces DI binding even if implicit service detection cannot classify the parameter.
MVC partial rendering
MVC apps can keep their existing Razor partials and render them from Heimdall content actions.
builder.Services.AddHeimdall(options =>
{
options.EnableDetailedErrors = true;
});
builder.Services.AddHeimdallMvc();
AddHeimdallMvc() adds MVC view services, IHttpContextAccessor, and IHeimdallMvcRenderer. It does not map controller routes; call MapControllerRoute(...) or MapControllers() separately if the app also serves normal MVC controllers.
[ContentInvocationPrefix("orders")]
public sealed class OrderActions(
IOrderRepository orders,
IHeimdallMvcRenderer views)
{
[ContentInvocation("filter")]
public async Task<IHtmlContent> Filter(OrderFilter filter, CancellationToken ct)
{
var results = await orders.SearchAsync(filter, ct);
return await views.PartialAsync("_OrderList", results, ct);
}
}
Partial names are resolved through the MVC view engine. You can also pass an application-relative path such as ~/Views/Orders/_OrderList.cshtml when you want to avoid controller-based lookup assumptions.
Server helpers cover the same trigger-routing and response directives supported by the Heimdall client runtime:
Html.Button(
"Close",
HeimdallHtml.OnClick("dialog.close"),
HeimdallHtml.Scope(HeimdallHtml.EventScope.Self)
);
return Html.Fragment(
HeimdallHtml.Abort("validation-failed"),
HeimdallHtml.Invocation("#errors", HeimdallHtml.Swap.Inner, ErrorList.Render(errors))
);
Use HeimdallHtml.Ignore(...) / .IgnoreAll() for heimdall-ignore, HeimdallHtml.Scope(...) for heimdall-scope, HeimdallHtml.Abort(...) to suppress the main swap, and HeimdallHtml.Redirect(...) for client navigation.
Endpoints (v1)
Heimdall.Server exposes the following same-origin endpoints:
Content actions
POST /__heimdall/v1/content/actions
Executes a server action and returns HTML (optionally containing <invocation> directives).
CSRF token
GET /__heimdall/v1/csrf
Returns an antiforgery token used by the Heimdall client runtime (cached client-side).
Bifrost (SSE)
GET /__heimdall/v1/bifrost?topic=...
Server-Sent Events stream for pushing HTML and/or <invocation> directives.
Bifrost publishes on the default Heimdall SSE event unless an event name is supplied:
await bifrost.PublishAsync("orders", Html.Span("Updated"), TimeSpan.FromSeconds(5));
await bifrost.PublishAsync("orders", "order.updated", Html.Span("Updated"), TimeSpan.FromSeconds(5));
Use topics for subscription and authorization boundaries. Use named events to route different message types inside the same topic stream.
Bifrost subscribe token
GET /__heimdall/v1/bifrost/token?topic=...
Issues a short-lived subscribe token gated by antiforgery.
Topic subscription can be authorized before a subscribe token is issued:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BifrostTopic", policy =>
policy.RequireAuthenticatedUser());
});
builder.Services.AddHeimdall(options =>
{
options.BifrostTopicPolicy = "BifrostTopic";
options.AuthorizeBifrostTopic = (ctx, topic) =>
ValueTask.FromResult(topic.StartsWith($"user:{ctx.User.Identity?.Name}:", StringComparison.Ordinal));
});
Policy handlers receive a BifrostTopicResource containing the topic and HttpContext. If both BifrostTopicPolicy and AuthorizeBifrostTopic are configured, both must allow the topic. Subscribe tokens are short-lived and bound to the current authenticated principal.
What this package provides
- Heimdall middleware
- Content action execution pipeline
- HTML response helpers
- Page mapping primitives (
MapHeimdallPage) - Antiforgery integration
- Bifrost (SSE) server runtime
- Hypermedia-driven UI execution model
Design philosophy
Heimdall intentionally moves UI back toward the browser’s original model:
- The browser requests documents
- The server produces documents
- Interactions request HTML, not data
- HTML is the contract
- Composition happens on the server
- Real-time updates stream HTML
This allows rich applications without SPA complexity.
Status
This package is currently alpha.
Public APIs, naming, and patterns may change.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- No dependencies.
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 |
|---|---|---|
| 2.0.1 | 76 | 5/29/2026 |
| 2.0.0 | 70 | 5/29/2026 |
| 1.0.7 | 96 | 5/26/2026 |
| 1.0.6 | 103 | 5/7/2026 |
| 1.0.5 | 88 | 5/6/2026 |
| 1.0.4 | 112 | 4/25/2026 |
| 1.0.3 | 93 | 4/25/2026 |
| 1.0.2 | 99 | 4/24/2026 |
| 1.0.1 | 98 | 4/16/2026 |
| 1.0.0 | 96 | 4/16/2026 |
| 0.2.3-alpha.1 | 58 | 4/13/2026 |
| 0.2.2-alpha.1 | 59 | 4/13/2026 |
| 0.2.1-alpha.1 | 57 | 4/13/2026 |
| 0.2.0-alpha.1 | 57 | 4/10/2026 |
| 0.1.9-alpha.1 | 59 | 4/10/2026 |
| 0.1.8-alpha.1 | 56 | 4/9/2026 |
| 0.1.7-alpha.1 | 61 | 4/9/2026 |
| 0.1.6-alpha.1 | 60 | 4/8/2026 |
| 0.1.5-alpha.1 | 56 | 4/7/2026 |
| 0.1.4-alpha.1 | 63 | 4/4/2026 |