TheAppManager 2.0.0

dotnet add package TheAppManager --version 2.0.0
                    
NuGet\Install-Package TheAppManager -Version 2.0.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.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TheAppManager" Version="2.0.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.0.0
                    
#r "nuget: TheAppManager, 2.0.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.0.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.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TheAppManager&version=2.0.0
                    
Install as a Cake Tool

<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 hostAppModuleTestHost 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
  • Zero external dependencies — built solely on Microsoft.AspNetCore.App framework reference

Installation

dotnet add package TheAppManager

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>();
});

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>();
    }
}

var modules = new AppModuleCollection()
    .Add<AuthModule>()
    .Add<WeatherModule>()
    .Replace<AuthModule, FakeAuthModule>();

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

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

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

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.0 270 3/10/2026

v2.0.0: Complete redesign — composable module system replacing the strategy pattern.