AndreGoepel.AppFoundation.Hosting
1.1.2
dotnet add package AndreGoepel.AppFoundation.Hosting --version 1.1.2
NuGet\Install-Package AndreGoepel.AppFoundation.Hosting -Version 1.1.2
<PackageReference Include="AndreGoepel.AppFoundation.Hosting" Version="1.1.2" />
<PackageVersion Include="AndreGoepel.AppFoundation.Hosting" Version="1.1.2" />
<PackageReference Include="AndreGoepel.AppFoundation.Hosting" />
paket add AndreGoepel.AppFoundation.Hosting --version 1.1.2
#r "nuget: AndreGoepel.AppFoundation.Hosting, 1.1.2"
#:package AndreGoepel.AppFoundation.Hosting@1.1.2
#addin nuget:?package=AndreGoepel.AppFoundation.Hosting&version=1.1.2
#tool nuget:?package=AndreGoepel.AppFoundation.Hosting&version=1.1.2
app-foundation
A reusable backend + management-frontend foundation for .NET 10 / Blazor apps, published as NuGet packages. A consuming app references the packages and gets authentication, user/role administration, a Blazor management shell, durable email, and .NET Aspire service defaults out of the box — then adds its own pages and branding on top.
- Reference consumer: andregoepel.dev
- Identity is provided by the companion AndreGoepel.Marten.Identity packages.
This repository used to host both the foundation and a website. The website has been extracted to its own repo; this repository is now purely the published packages.
Packages
| Package | Purpose |
|---|---|
AndreGoepel.AppFoundation |
Razor Class Library — the management frontend: layout, navigation, setup, dashboard, error pages. Brand/extend via AppFoundationLayoutOptions. |
AndreGoepel.AppFoundation.Hosting |
Umbrella backend seam — AddAppFoundation / UseAppFoundation. Transitively pulls in the other three packages + AndreGoepel.Marten.Identity.Blazor. |
AndreGoepel.AppFoundation.MailService |
Email via a Wolverine handler + MailKit SMTP, backed by a durable Marten outbox. |
AndreGoepel.AppFoundation.ServiceDefaults |
.NET Aspire service defaults: OpenTelemetry, health checks, HTTP resilience, service discovery. |
All four are published to NuGet with lockstep versioning. A host typically references
AndreGoepel.AppFoundation.Hosting (for the wiring) and AndreGoepel.AppFoundation (for
the UI components it renders directly).
Stack
| Layer | Technology |
|---|---|
| Frontend | Blazor (.NET 10), Radzen components |
| Backend | ASP.NET Core (.NET 10) |
| Persistence | Marten (PostgreSQL document + event store) |
| Messaging / CQRS | Wolverine (durable outbox) |
| MailKit | |
| Orchestration | .NET Aspire |
| Authentication | ASP.NET Core Identity, event-sourced (marten-identity) |
Using it in a host app
Reference the packages (versions are centrally managed in your repo):
<PackageReference Include="AndreGoepel.AppFoundation.Hosting" />
<PackageReference Include="AndreGoepel.AppFoundation" />
<PackageReference Include="AndreGoepel.Marten.Identity.Blazor" />
Wire the foundation in Program.cs:
builder.AddAppFoundation(options =>
{
options.DatabaseConnectionName = "appfoundation-database"; // default
// options.WolverineServiceName, options.SecretsDirectory ...
});
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// Brand the management shell and contribute your own admin menu entries.
builder.Services.Configure<AppFoundationLayoutOptions>(o =>
{
o.BrandName = "your.app";
o.Copyright = "your.app © 2026";
o.AdminMenu = typeof(YourAdminMenu); // optional Razor component
});
var app = builder.Build();
app.UseAppFoundation();
app.MapStaticAssets();
app.MapRazorComponents<App>() // your root App.razor
.AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(
typeof(AppFoundationLayoutOptions).Assembly, // AppFoundation UI
typeof(AndreGoepel.Marten.Identity.Blazor.Initialization).Assembly // identity pages
);
app.MapAdditionalIdentityEndpoints();
The host owns its root App.razor and Routes.razor; the router includes the AppFoundation
and Marten.Identity.Blazor assemblies and uses AppFoundation's MainLayout for the
authenticated management area. See andregoepel.dev
for a complete, working example.
Configuration
A PostgreSQL connection string is required, by default under
ConnectionStrings:appfoundation-database.
AppFoundationOptions (backend seam):
| Option | Default | Purpose |
|---|---|---|
DatabaseConnectionName |
appfoundation-database |
Connection-string name to read |
WolverineServiceName |
AppFoundation |
Service name for the durable inbox/outbox |
SecretsDirectory |
/run/secrets |
Key-per-file secrets directory (see below) — from 1.1.0 |
AppFoundationLayoutOptions (management shell branding):
BrandName, LogoPath, Copyright, and AdminMenu (a Razor component type rendered as
extra administrator nav entries).
Docker secrets (from 1.1.0)
Configuration — notably the connection string — can be supplied as files instead of
plaintext environment variables. AddAppFoundation loads SecretsDirectory (default
/run/secrets) key-per-file with optional: true (a no-op when absent, so local
development is unaffected). A secret named ConnectionStrings__appfoundation-database
(content = the full connection string; __ maps to the : section separator) is read
straight into configuration and never enters the process environment.
services:
app:
image: ghcr.io/you/your-app:latest
environment:
- ASPNETCORE_ENVIRONMENT=Production
secrets:
- ConnectionStrings__appfoundation-database
secrets:
ConnectionStrings__appfoundation-database:
file: ./secrets/connectionstring # chmod 600; or `external: true` under Swarm
Repository layout
src/
AndreGoepel.AppFoundation/ # management-frontend RCL
AndreGoepel.AppFoundation.Hosting/ # umbrella backend seam
AndreGoepel.AppFoundation.MailService/ # Wolverine + MailKit email
AndreGoepel.AppFoundation.ServiceDefaults/
tests/
AndreGoepel.AppFoundation.Tests/
AndreGoepel.AppFoundation.MailService.Tests/
Build & release
dotnet restore # --locked-mode in CI
dotnet build -c Release
dotnet test -c Release
- Central Package Management with committed
packages.lock.json; CI restores in--locked-mode, runs a vulnerability gate, and pins actions to commit SHAs. - Publishing: push a
vX.Y.Ztag → CI packs all four packages and publishes them to NuGet via OIDC trusted publishing (no stored API key).
Architecture notes
Why Marten? PostgreSQL as a document store removes the ORM mapping layer for most use cases while keeping relational queries available, with event sourcing built in. No separate NoSQL infrastructure.
Why Wolverine? Clean handler dispatch with built-in message persistence, retries and outbox support — async messaging from day one, not bolted on later.
Why .NET Aspire? Local orchestration and service discovery that map cleanly to cloud deployment targets.
Why a modular monolith? Modules are separated by namespace and handler boundary, not by network boundary. Splitting later is possible; splitting prematurely adds operational overhead before there's a scaling problem that justifies it.
License
MIT — use freely, attribution appreciated but not required.
Built by André Göpel — Senior Web Engineer · .NET & Blazor
| 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
- AndreGoepel.AppFoundation (>= 1.1.2)
- AndreGoepel.AppFoundation.MailService (>= 1.1.2)
- AndreGoepel.AppFoundation.ServiceDefaults (>= 1.1.2)
- AndreGoepel.Marten.Identity.Blazor (>= 1.1.3)
- Marten (>= 9.12.0)
- Microsoft.AspNetCore.HeaderPropagation (>= 10.0.9)
- Radzen.Blazor (>= 11.1.0)
- WolverineFx.Marten (>= 6.16.0)
- WolverineFx.RuntimeCompilation (>= 6.16.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.