TheAppManager 2.2.0
See the version list below for details.
dotnet add package TheAppManager --version 2.2.0
NuGet\Install-Package TheAppManager -Version 2.2.0
<PackageReference Include="TheAppManager" Version="2.2.0" />
<PackageVersion Include="TheAppManager" Version="2.2.0" />
<PackageReference Include="TheAppManager" />
paket add TheAppManager --version 2.2.0
#r "nuget: TheAppManager, 2.2.0"
#:package TheAppManager@2.2.0
#addin nuget:?package=TheAppManager&version=2.2.0
#tool nuget:?package=TheAppManager&version=2.2.0
<p align="center"> <img src="logo.png" alt="TheAppManager" width="128" /> </p>
TheAppManager
A lightweight, composable module system for ASP.NET Core application startup.
Table of Contents
- Overview
- Installation
- Quick Start
- Creating Modules
- Auto-Discovery
- Multi-Assembly Scanning
- Testing
- Async Support
- Builder Configuration Hook
- Advanced: Using AppManagerBuilder
- Project Structure
- Contributing
- License
Overview
TheAppManager lets you organize your ASP.NET Core startup into independent, composable modules. Each module encapsulates its own services, middleware, and endpoints — keeping Program.cs clean and making features reusable across projects.
Architecture
┌──────────────────────┐
│ AppManager.Start() │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ AppManagerBuilder │
│ ┌─────────────────┐ │
│ │ WebApplication │ │
│ │ Builder │ │
│ └─────────────────┘ │
└──────────┬───────────┘
│ applies in registration order
┌────────────────┼────────────────┐
│ │ │
┌─────────▼──────┐ ┌──────▼───────┐ ┌──────▼───────┐
│ SwaggerModule │ │ AuthModule │ │ WeatherModule│
│ ├ Services │ │ ├ Services │ │ ├ Services │
│ └ Middleware │ │ └ Middleware │ │ └ Endpoints │
└────────────────┘ └──────────────┘ └──────────────┘
Why Modules?
As ASP.NET Core applications grow, Program.cs accumulates unrelated service registrations, middleware, and endpoint mappings. Modules solve this by letting each feature own its startup logic:
- Composable — add, remove, or reorder modules without touching other code
- Reusable — package a module in a NuGet library and share it across projects
- Testable — each module can be tested in isolation with
AppModuleTestHost - Transparent ordering — modules run in the order you register them, just like middleware
Features
- Composable modules — encapsulate services, middleware, and endpoints into independent
IAppModuleimplementations - Registration-order execution — modules are applied in the order you add them, no magic priority numbers
- Auto-discovery — automatically find and register all
IAppModuleimplementations in your assembly - Multi-assembly scanning — discover modules from referenced assemblies with
AddFromAssemblyOf<T>() - Conditional registration — add modules only when conditions are met with
AddIf<T>(bool) - Module replacement — swap modules for test doubles with
Replace<TOld, TNew>() - Test host —
AppModuleTestHostbuilds a TestServer from modules for integration testing - Fluent API — chain module registrations for readable startup code
- Async support — run applications asynchronously with
StartAsyncandRunAsync - Builder hook — customize
WebApplicationBuilderbefore modules are applied - Minimal dependencies — built on the
Microsoft.AspNetCore.Appframework reference, plusMicrosoft.AspNetCore.TestHost, which backsAppModuleTestHostand ships with the package
Installation
dotnet add package TheAppManager
Usage
Clone the repository and run the bundled sample, which wires up SwaggerModule, AuthModule, and WeatherModule through AppManager.Start:
dotnet run --project samples/TheAppManager.Sample
This starts an ASP.NET Core app with Swagger UI (in development) and a /weatherforecast endpoint, each contributed by its own IAppModule — no logic in Program.cs beyond the module list. In your own project, the minimal integration looks like:
using TheAppManager.Startup;
AppManager.Start(args, modules =>
{
modules
.Add<SwaggerModule>()
.Add<AuthModule>()
.Add<WeatherModule>();
});
See Creating Modules below for how to implement IAppModule.
Quick Start
Register modules explicitly in Program.cs:
using TheAppManager.Startup;
AppManager.Start(args, modules =>
{
modules
.Add<SwaggerModule>()
.Add<AuthModule>()
.Add<WeatherModule>();
});
Or let TheAppManager discover modules automatically:
using TheAppManager.Startup;
AppManager.Start(args);
Creating Modules
A Minimal Module
Implement IAppModule and override only what you need. All methods have default no-op implementations:
using TheAppManager.Modules;
public class SwaggerModule : IAppModule
{
public void ConfigureServices(WebApplicationBuilder builder)
{
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
}
public void ConfigureMiddleware(WebApplication app)
{
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
}
}
A module that only registers endpoints:
public class WeatherModule : IAppModule
{
public void ConfigureServices(WebApplicationBuilder builder)
{
builder.Services.AddScoped<WeatherForecastService>();
}
public void ConfigureEndpoints(IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/weatherforecast", (WeatherForecastService svc) =>
Results.Ok(svc.GetForecasts()));
}
}
Note that ConfigureServices receives WebApplicationBuilder (not just IServiceCollection), giving you access to builder.Configuration, builder.Environment, and builder.Host when you need them.
Module Ordering
Modules are applied in registration order — the order you call Add<T>(). This is the same principle as ASP.NET Core middleware: what you register first runs first.
AppManager.Start(args, modules =>
{
modules
.Add<SwaggerModule>() // services & middleware first
.Add<AuthModule>() // auth middleware second
.Add<WeatherModule>(); // endpoints last
});
Conditional Modules
Use AddIf<T>() to register modules based on runtime conditions:
var isDev = builder.Environment.IsDevelopment();
AppManager.Start(args, modules =>
{
modules
.AddIf<SwaggerModule>(isDev)
.Add<AuthModule>()
.Add<WeatherModule>();
});
The configure callback runs before the WebApplicationBuilder exists. To decide from the environment or configuration, pass a predicate; it runs once, when the app is built, against the real builder (after any ConfigureBuilder callbacks):
AppManager.Start(args, modules => modules
.AddIf<SwaggerModule>(b => b.Environment.IsDevelopment()));
A module swapped in with Replace<TOld, TNew>() keeps the predicate of the module it replaces.
Auto-Discovery
When you call AppManager.Start(args) without a configure callback, TheAppManager scans the entry assembly for all concrete classes implementing IAppModule with a parameterless constructor and registers them automatically (sorted alphabetically by type name for deterministic ordering).
This is convenient for applications where module ordering doesn't matter, or where you're fine with alphabetical ordering.
// Discovers and registers all IAppModule implementations in the entry assembly
AppManager.Start(args);
For order-sensitive applications, prefer explicit registration.
Multi-Assembly Scanning
When your modules live in referenced libraries, scan their assemblies explicitly:
AppManager.Start(args, modules =>
{
// Scan the assembly containing SwaggerModule for all modules
modules.AddFromAssemblyOf<SwaggerModule>();
// Or scan a specific assembly
modules.AddFromAssembly(typeof(SomeLibraryModule).Assembly);
// Mix with explicit registration
modules.Add<MyAppModule>();
});
Testing
Integration Testing with AppModuleTestHost
AppModuleTestHost builds a TestServer from your modules, making integration tests simple:
using TheAppManager.Testing;
[Fact]
public async Task WeatherEndpoint_ReturnsForecasts()
{
await using var host = await new AppModuleTestHost()
.Add<WeatherModule>()
.StartAsync();
var client = host.GetTestClient();
var response = await client.GetAsync("/weatherforecast");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
You can also resolve services directly:
await using var host = await new AppModuleTestHost()
.Add<WeatherModule>()
.StartAsync();
var service = host.GetRequiredService<WeatherForecastService>();
Replacing Modules in Tests
Use Replace<TOld, TNew>() to swap a real module for a test double:
public class FakeAuthModule : IAppModule
{
public void ConfigureServices(WebApplicationBuilder builder)
{
builder.Services.AddSingleton<IAuthService, FakeAuthService>();
}
}
await using var host = await new AppModuleTestHost()
.Add<AuthModule>()
.Add<WeatherModule>()
.Replace<AuthModule, FakeAuthModule>()
.StartAsync();
Async Support
Use StartAsync for async startup:
await AppManager.StartAsync(args, modules =>
{
modules
.Add<SwaggerModule>()
.Add<WeatherModule>();
});
Builder Configuration Hook
Customize the WebApplicationBuilder before modules are applied:
AppManager.Start(
args,
modules =>
{
modules
.Add<SwaggerModule>()
.Add<WeatherModule>();
},
builder =>
{
builder.Configuration.AddJsonFile("custom-settings.json", optional: true);
});
Advanced: Using AppManagerBuilder
For full control, use AppManagerBuilder directly:
using TheAppManager.Modules;
using TheAppManager.Startup;
var modules = new AppModuleCollection()
.Add<SwaggerModule>()
.Add<AuthModule>()
.Add<WeatherModule>();
var appManager = new AppManagerBuilder(args)
.ConfigureBuilder(builder =>
{
builder.Configuration.AddJsonFile("custom-settings.json");
})
.Build(modules);
await appManager.RunAsync();
Project Structure
src/TheAppManager/ → Class library (NuGet package)
samples/TheAppManager.Sample/ → Sample web app demonstrating usage
tests/TheAppManager.Tests/ → Unit and integration tests
Tech Stack
- C#
- Swashbuckle.AspNetCore
- Microsoft.AspNetCore.TestHost
- Microsoft.AspNetCore.Mvc.Testing
- Shouldly
- xunit
- xunit.runner.visualstudio
Roadmap
- Module dependency declarations (e.g.
DependsOn<T>()) to enforce ordering automatically - Health-check integration for modules that expose their own health endpoints
- Source-generator based auto-discovery to avoid runtime assembly scanning
- Additional samples (minimal API-only, Blazor Server) alongside the existing sample app
- Configuration validation hooks per module (fail fast on missing settings)
- v3: move
AppModuleTestHostto a separateTheAppManager.Testingpackage so production apps no longer shipMicrosoft.AspNetCore.TestHost(breaking change)
See the open issues for details and to propose new ideas.
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run tests (
dotnet test) - Commit your changes (
git commit -am 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
License
Built by Atypical Consulting. We also make NuGetKeep, a self-hosted NuGet server with supply-chain quarantine.
| 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
- Microsoft.AspNetCore.TestHost (>= 10.0.13)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on TheAppManager:
| Package | Downloads |
|---|---|
|
TheAppManager.Testing
Test helpers for TheAppManager: a production-faithful, TestServer-backed AppModuleTestHost and WebApplicationFactory module replacement. |
GitHub repositories
This package is not used by any popular GitHub repositories.