TheAppManager 2.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package TheAppManager --version 2.2.0
                    
NuGet\Install-Package TheAppManager -Version 2.2.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="TheAppManager" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TheAppManager" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="TheAppManager" />
                    
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 TheAppManager --version 2.2.0
                    
#r "nuget: TheAppManager, 2.2.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 TheAppManager@2.2.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=TheAppManager&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=TheAppManager&version=2.2.0
                    
Install as a Cake Tool

TheAppManager banner

<p align="center"> <img src="logo.png" alt="TheAppManager" width="128" /> </p>

TheAppManager

A lightweight, composable module system for ASP.NET Core application startup.

CI NuGet License: MIT

Table of Contents

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 IAppModule implementations
  • Registration-order execution — modules are applied in the order you add them, no magic priority numbers
  • Auto-discovery — automatically find and register all IAppModule implementations 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 — AppModuleTestHost builds a TestServer from modules for integration testing
  • Fluent API — chain module registrations for readable startup code
  • Async support — run applications asynchronously with StartAsync and RunAsync
  • Builder hook — customize WebApplicationBuilder before modules are applied
  • Minimal dependencies — built on the Microsoft.AspNetCore.App framework reference, plus Microsoft.AspNetCore.TestHost, which backs AppModuleTestHost and 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 AppModuleTestHost to a separate TheAppManager.Testing package so production apps no longer ship Microsoft.AspNetCore.TestHost (breaking change)

See the open issues for details and to propose new ideas.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes
  4. Run tests (dotnet test)
  5. Commit your changes (git commit -am 'Add my feature')
  6. Push to the branch (git push origin feature/my-feature)
  7. Open a Pull Request

License

MIT


Built by Atypical Consulting. We also make NuGetKeep, a self-hosted NuGet server with supply-chain quarantine.

Product 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. 
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 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.

Version Downloads Last Updated
3.0.0 117 10/18/2026
2.2.0 200 10/18/2026
2.1.0 94 10/18/2026
2.0.0 2,187 3/10/2026