Skip to content

Pragmatic.Composition

Source-generated application composition and dependency injection for .NET 10. Declare modules, services, and startup steps; the generator writes all the wiring, so the composed application starts without reflection and without scanning assemblies.

Convention-based scanning lives in a separate package, Pragmatic.Composition.Scanning, for the cases the generator cannot see. It loads assemblies and enumerates types at run time and is neither trim- nor AOT-safe; referencing it is the opt-in, and nothing in the generated path calls it.

Every .NET app accumulates the same startup ceremony: register services one by one, order middleware, wire DbContexts, keep it consistent across modules. With 50+ services, Program.cs becomes a wall of services.AddScoped<>() that no one wants to maintain, and forgetting one line silently breaks the app.

// Without Pragmatic: 80+ lines of mechanical wiring
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IProductService, ProductService>();
// ... 50 more, growing with every new class
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(cs));
app.UseAuthentication(); app.UseAuthorization(); // forget one and it breaks

Declare what your application is; the generator handles how it starts up.

// Program.cs: one call wires everything
await PragmaticApp.RunAsync(args, app =>
{
app.UsePragmaticMigrations();
if (app.Environment.IsDevelopment()) app.UseDevelopmentIdentity(); // X-User-* headers are the caller
});
// Module topology: one class per bounded context
[Module]
[Include<OrdersModule, AppDatabase>]
[Include<BillingModule, FinancialDatabase>]
public sealed class MyAppModule;
// Services register themselves
[Service]
public class OrderService(IOrderRepository repository) : IOrderService { }

The generator emits the complete host startup (infrastructure auto-registration, ordered startup steps, database init, endpoint mapping, telemetry, maintenance mode) at compile time. Cross-assembly discovery works through [PragmaticMetadata] assembly attributes: libraries declare what they register, the host aggregates automatically.

Package Role
Pragmatic.Composition Meta-package (references Abstractions + Host)
Pragmatic.Composition.Host ASP.NET Core runtime: PragmaticApp, IStartupStep, telemetry, remote boundaries, maintenance mode
Pragmatic.Composition.Scanning Opt-in convention scanning at run time, not trim- or AOT-safe (see above)

Attributes ([Service], [Module], [StartupStep], …) live in Pragmatic.Abstractions, so domain modules use them without referencing ASP.NET Core.

Tier Where What it decides
Topology (compile-time) [Module], [Boundary], [BelongsTo<T>], [UsePackage<T>] Structure & module dependencies; the generator detects it
Module strategy Program.cs via IPragmaticBuilder.Use*() Infrastructure choices: auth handler, storage, transport
Business wiring IStartupStep Services, filters, OpenAPI, feature-specific DI + HTTP pipeline

What a module declares is wired without a line in Program.cs, with in-memory or passthrough defaults where a backend is optional; you override only what you need. Two things are never defaulted, because guessing them would be unsafe: who calls the API (every endpoint requires an authenticated caller, the build stops on PRAG1695 without an identity package or [AnonymousHost], and outside Development a host with no authentication method refuses to start) and a culture, from UseI18N or the modules’ translations. See Startup Pipeline.

Terminal window
dotnet add package Pragmatic.Composition.Host
dotnet add package Pragmatic.SourceGenerator # the unified analyzer

(Building inside this monorepo? See Monorepo Structure.)

  • [Service] / [Decorator]: self-registering services and ordered decorators, no AddScoped lists.
  • [Module] / [Include<…>]: module topology and per-module databases.
  • [StartupStep] / IStartupStep: ordered business wiring + HTTP pipeline configuration.
  • IPragmaticBuilder.Use*(): module strategy (auth, storage, messaging, …).
  • Remote boundaries: [RemoteBoundary<T>] generates typed HTTP invokers for cross-service calls.
  • Maintenance mode, telemetry, and database initialization, wired automatically.

Functional within 1.0.0-alpha: the composition model, service/decorator registration, startup steps, the builder, remote boundaries, and maintenance mode. See the roadmap.

| Concepts | Composition model, metadata aggregation, the 3-tier configuration model | | Getting Started | Your first module, service, and PragmaticApp.RunAsync | | Service Registration | [Service], lifetimes, [Decorator], keyed services | | Startup Pipeline | IStartupStep, IPragmaticBuilder, ordering, HTTP pipeline | | Remote Boundaries | [RemoteBoundary<T>], typed HTTP invokers, the invoke endpoint | | Common Mistakes | The most frequent composition pitfalls | | Troubleshooting | Problem/solution guide with diagnostics |

  • .NET 10.0+
  • ASP.NET Core 10.0+
  • Pragmatic.SourceGenerator analyzer

Part of the Pragmatic.Design ecosystem. See Licensing. Pragmatic.Composition is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).