Troubleshooting
Practical problem/solution guide for Pragmatic.Composition. Each section covers a common issue, the likely causes, and the fix.
Services Not Resolving (DI Errors at Runtime)
Section titled “Services Not Resolving (DI Errors at Runtime)”Your application starts but a service resolution fails with InvalidOperationException: Unable to resolve service for type 'IMyService'.
Checklist
Section titled “Checklist”-
Is the class decorated with
[Service]? Without the attribute, the SG does not generate a registration. Add[Service]to the class. -
Does the class implement an interface? The default behavior registers the service as its first interface. If the class has no interfaces,
PRAG1643fires. Use[Service(AsSelf = true)]for concrete registration, or implement an interface. -
Is
Pragmatic.Compositionreferenced in the library project? The SG only generates metadata when the package is referenced. DiagnosticPRAG1605fires when[Service]is found without the Composition reference. -
Is the library project referenced by the host? The host SG reads
[PragmaticMetadata]from referenced assemblies. If the library is not referenced (directly or transitively), its services are not discovered. -
Check the generated code. Open
obj/Debug/net10.0/generated/and look for_Infra.DI.ServiceRegistration.g.csin the library project. Verify the service appears in theAdd{Prefix}Services()method. In the host project, checkHost.Services.g.csfor the aggregated registration. -
Is the service lifetime correct? A
Transientservice resolved viaIServiceProvider(not a scope) will work, but aScopedservice resolved outside a scope throws. Verify the resolution context matches the lifetime.
Module Not Detected by the Source Generator
Section titled “Module Not Detected by the Source Generator”The host builds but the SG does not include a module’s services, repositories, or actions.
Checklist
Section titled “Checklist”-
Does the module class have
[Module]? The attribute is required for SG detection. Without it, the module is invisible. -
Does the host module have
[Include<TModule>]or[Include<TModule, TDatabase>]? Library-level[Module]declares the module, but the host must explicitly include it. -
Is the module assembly referenced by the host project? Check the
.csprojfor a<ProjectReference>or<PackageReference>to the module’s assembly. -
Check
Host.Topology.g.cs(Debug builds only). This file contains a topology report listing all discovered modules, their boundaries, and registrations. If your module is not listed, it was not discovered. -
Diagnostic
PRAG1690reports the count of discovered modules. If it says “Discovered 0 Pragmatic modules”, no[PragmaticMetadata]attributes were found in any referenced assembly.
Possible Causes
Section titled “Possible Causes”- The module project does not reference
Pragmatic.CompositionorPragmatic.Abstractions, so no metadata is generated. - The module project references the SG but the SG is not configured correctly (
OutputItemType="Analyzer"missing). - The host references an older build of the module that was compiled before
[PragmaticMetadata]attributes were generated. Clean and rebuild.
StartupStep Not Executing
Section titled “StartupStep Not Executing”Your IStartupStep implementation exists, but ConfigureServices or ConfigurePipeline is never called.
Checklist
Section titled “Checklist”-
Does the class have
[StartupStep]? The attribute is required for discovery. ImplementingIStartupStepalone is not enough. -
Does the class implement
IStartupStep? The attribute without the interface triggersPRAG1630. Both are required. -
Is the step in a library or host project?
- Library project: The step is registered via
[PragmaticMetadata]. Verify the library is referenced by the host. - Host project: The step is processed directly. Verify it builds without errors.
- Library project: The step is registered via
-
Check the
Ordervalue. Steps with the sameOrderexecute in an undefined order relative to each other. If your step depends on another step’s services, ensure yourOrderis higher. -
Diagnostic
PRAG1694fires when no[StartupStep]registrations are found. If this appears, no steps are being discovered from any referenced assembly.
Pipeline Middleware in Wrong Order
Section titled “Pipeline Middleware in Wrong Order”Authentication fails, CORS headers are missing, or middleware behaves unexpectedly.
Checklist
Section titled “Checklist”-
Check the step
Ordervalues.ConfigurePipelinecalls execute in ascendingOrder. Authentication (typically Order 100) must come before authorization (110). Routing (50) must come before endpoint-dependent middleware. -
Review the effective pipeline. For Debug builds, check
Host.Topology.g.csfor the ordered list of steps. The generatedConfigurePipelinemethod inHost.Services.g.csshows the exact call sequence. -
Built-in steps have fixed orders: ResponseCompression (25), Routing (50), CORS (75). Your steps must work around these.
-
Multiple steps with the same Order. This is valid but the relative order between them is non-deterministic. If order matters, assign different
Ordervalues.
Common Pipeline Order Issues
Section titled “Common Pipeline Order Issues”| Symptom | Cause | Fix |
|---|---|---|
| 401 on all requests | Auth middleware after endpoint mapping | Set auth step Order < endpoint mapping |
| CORS headers missing | CORS step after routing | Use built-in CorsStep (Order 75) |
| Tenant ID always null | Tenant resolution before authentication | Set tenant step Order > auth step |
| Response not compressed | Compression after response started | Use built-in ResponseCompressionStep (Order 25) |
Auto-Registration Not Working for Infrastructure Modules
Section titled “Auto-Registration Not Working for Infrastructure Modules”You added a Pragmatic infrastructure package (e.g., Pragmatic.MultiTenancy) but the default registration is not happening.
Checklist
Section titled “Checklist”-
Is the package referenced in the host project’s
.csproj? The SG only runs in the host project. Infrastructure packages must be referenced (directly or transitively) from the host. -
Is the SG analyzer configured? The host project must have:
<ProjectReference Include="..\Pragmatic.SourceGenerator\...\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" /> -
Check
Host.Services.g.cs. Look forRegisterAllPragmaticServices. It should contain calls likeservices.AddPragmaticMultiTenancy()for each detected module. -
Verify
FeatureDetectordetection. The SG detects packages by checking for well-known types viaGetTypeByMetadataName(). If the type is not found (e.g., the package API changed), detection fails silently. -
Clean and rebuild. SG output is cached aggressively. After adding a new package reference, a clean rebuild ensures the SG re-evaluates all features.
Host Fails to Start
Section titled “Host Fails to Start”The application crashes or enters maintenance mode on startup.
Checklist
Section titled “Checklist”-
Check
/maintenanceendpoint. If maintenance mode is enabled (default), the application serves diagnostic info at/maintenanceinstead of crashing. Navigate tohttp://localhost:{port}/maintenancefor error details. -
Missing configuration. Steps with
[RequiresConfig("path")]cause fail-fast validation. The error message lists all missing keys:Missing required configuration keys: ConnectionStrings:App, Jwt:Key.Add the missing keys to
appsettings.jsonor environment variables. -
Database connection failure. If
UseDatabaseEnsureCreated()orUseDatabaseMigrate()is enabled, a database connection error at startup will trigger maintenance mode. Verify the connection string and database availability. -
Circular dependency in DI. If a service’s constructor creates a circular chain (
A -> B -> C -> A), the DI container throws at resolution time. CheckPRAG1621for[Inject]-based circular references detected at compile time. Constructor-based circularity is only caught at runtime. -
Check the exception. If maintenance mode is disabled, the application crashes with a stack trace. Read the exception message. Common causes:
InvalidOperationException: Unable to resolve service— missing DI registrationSqlException: Cannot open database— wrong connection stringArgumentException: An item with the same key has already been added— duplicate keyed service
Boundary Services Not Isolated
Section titled “Boundary Services Not Isolated”Services from one module are accidentally resolving services from another module’s boundary.
Possible Causes
Section titled “Possible Causes”-
Missing
[BelongsTo]. Without boundary annotation, types float freely and may be registered in the wrong context. -
Direct reference instead of interface. If Module A directly references a concrete class from Module B (instead of depending on an interface), the boundary isolation is broken.
-
Shared database. When two modules use
[Include<TModule, SameDatabase>], they share a DbContext. This is by design — their entities are co-located. If isolation is needed, use separate databases.
Best Practice
Section titled “Best Practice”Modules should communicate through action invokers (IDomainActionInvoker<T>), not direct service references. This ensures boundary isolation and enables future distribution via [RemoteBoundary].
Feature Detection Missing Modules
Section titled “Feature Detection Missing Modules”A Pragmatic package is referenced but the SG does not detect it.
Possible Causes
Section titled “Possible Causes”-
Transitive reference too deep. The SG checks
compilation.GetTypeByMetadataName()for specific marker types. If the package is referenced transitively but its types are not visible in the compilation, detection fails. -
Version mismatch. The SG looks for specific FQN strings (e.g.,
Pragmatic.MultiTenancy.ITenantResolver). If the package renamed or moved the type, detection breaks. Ensure all Pragmatic packages are on the same version. -
SG not updated. If you updated the runtime package but not the SG, the SG may not know about new feature markers. Keep
Pragmatic.SourceGeneratorversion aligned with runtime packages.
Diagnostics Reference
Section titled “Diagnostics Reference”All Composition diagnostics use the PRAG16xx range:
| ID | Severity | Category | Description |
|---|---|---|---|
| PRAG1050 | Error | Package | Duplicate [UsePackage<T>] on module |
| PRAG1600 | Error | Topology | Module requires unavailable middleware |
| PRAG1601 | Error | Topology | Module dependency not declared |
| PRAG1602 | Error | Topology | Circular dependency detected |
| PRAG1605 | Warning | Reference | [Service] without Pragmatic.Composition reference |
| PRAG1606 | Error | Reference | [Decorator] requires Pragmatic.Composition reference |
| PRAG1610 | Error | Schema | Incompatible metadata schema version |
| PRAG1611 | Warning | Schema | Newer metadata schema version than supported |
| PRAG1612 | Info | Schema | Legacy metadata schema version (backward compat) |
| PRAG1620 | Error | Injection | [Inject] references unregistered service |
| PRAG1621 | Error | Injection | [Inject] creates circular reference |
| PRAG1630 | Error | Startup | [StartupStep] must implement IStartupStep |
| PRAG1631 | Error | Startup | [StartupStep] must be on a class |
| PRAG1632 | Error | Startup | [NeedsStep<T>] references unavailable step type |
| PRAG1640 | Error | Service | [Service] requires a class type |
| PRAG1641 | Warning | Service | Dependency not registered |
| PRAG1642 | Warning | Service | Singleton depends on scoped service (captive dependency) |
| PRAG1643 | Warning | Service | No interface found for service |
| PRAG1644 | Info | Service | Service registered (verbose) |
| PRAG1645 | Error | Service | Abstract class cannot be a service |
| PRAG1646 | Warning | Service | Keyed services require .NET 8+ |
| PRAG1651 | Warning | Database | Boundary has no database configured |
| PRAG1652 | Error | Database | DbContext name collision across different databases |
| PRAG1660 | Error | Decorator | Decorator must implement at least one interface |
| PRAG1661 | Error | Decorator | Decorator missing inner service constructor parameter |
| PRAG1670 | Error | Events | [EventHandler] on class not implementing IDomainEventHandler<T> |
| PRAG1680 | Warning | Package | [ExposeEndpoint<T>] references non-package action |
| PRAG1685 | Error | Remote | [RemoteBoundary<T>] and [Include<T>] on same module |
| PRAG1686 | Warning | Remote | [RemoteBoundary<T>] module has no actions |
| PRAG1687 | Info | Remote | [RemoteBoundary<T>] base URL not configured |
| PRAG1690 | Info | Discovery | Discovered Pragmatic modules count |
| PRAG1691 | Info | Discovery | Module composition info |
| PRAG1693 | Info | Discovery | No [Service] or [Decorator] registrations found |
| PRAG1694 | Info | Discovery | No [StartupStep] registrations found |
Generated Code Not Compiling
Section titled “Generated Code Not Compiling”The build fails with errors in SG-generated files (Host.Entry.g.cs or Host.Services.g.cs).
Checklist
Section titled “Checklist”-
Are all Pragmatic packages on the same version? Mismatched versions between runtime packages and the SG can produce incompatible generated code. Ensure
Pragmatic.SourceGenerator,Pragmatic.Abstractions,Pragmatic.Composition, and all runtime packages share the same version. -
Is the SG analyzer reference correct? The
.csprojmust have:<ProjectReference Include="..\Pragmatic.SourceGenerator\...\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" />Missing
OutputItemType="Analyzer"means the SG runs as a normal reference, not as a code generator. -
Check for namespace conflicts. If your project defines a type named
PragmaticApporPragmaticHost, it collides with the generated types. The SG generates into thePragmatic.Composition.Hostingnamespace for entry points and into the host’s root namespace forPragmaticHost. -
Clean and rebuild. SG caching can produce stale output. Run
dotnet cleanfollowed bydotnet buildto force a full regeneration.
Remote Boundary Actions Returning Errors
Section titled “Remote Boundary Actions Returning Errors”Actions invoked via [RemoteBoundary] return RemoteError instead of the expected business error.
Checklist
Section titled “Checklist”-
Is the remote host running? Verify the remote service is accessible at the configured URL. Check
appsettings.json:{"Pragmatic": {"RemoteBoundaries": {"Billing": { "BaseUrl": "https://billing-service:5001" }}}} -
Is the action registered on the remote host? The remote host must include the module locally (
[Include<BillingModule, BillingDatabase>]). Without it, the/_pragmatic/invokeendpoint cannot dispatch the action. -
Check the
RemoteError.Code. The error code indicates the failure point:Code Meaning Fix REMOTE_INVOKE_FAILEDHTTP call failed Check network, URL, firewall REMOTE_DESERIALIZE_FAILEDResponse could not be deserialized Check JSON serialization compatibility REMOTE_UNKNOWN_ERRORError with no ProblemDetails Check remote host logs REMOTE_ERRORBusiness error from remote Error propagated correctly; inspect the Title/Description -
Configure the HttpClient. Add timeout and retry policies to the named HttpClient:
services.AddHttpClient("Pragmatic.Remote.Billing", client =>{client.Timeout = TimeSpan.FromSeconds(30);});
Why does my Program.cs only have two lines?
Section titled “Why does my Program.cs only have two lines?”PragmaticApp.RunAsync(args, configure) is a stub method. The source generator replaces it with a complete host startup implementation based on discovered modules, services, and configuration. All the registration code lives in the generated Host.Entry.g.cs and Host.Services.g.cs files.
How do I see what the SG generated?
Section titled “How do I see what the SG generated?”In Visual Studio, expand Dependencies > Analyzers > Pragmatic.SourceGenerator in Solution Explorer. All generated files are listed there. Alternatively, look in obj/Debug/net10.0/generated/Pragmatic.SourceGenerator/ on disk.
Can I mix PragmaticApp.RunAsync with manual service registration?
Section titled “Can I mix PragmaticApp.RunAsync with manual service registration?”Yes, via IStartupStep.ConfigureServices. The step runs after SG defaults and IPragmaticBuilder, so you can register additional services or override existing ones. You cannot directly modify the WebApplicationBuilder before Build() — use IPragmaticBuilder.Services for that.
What happens if two modules register the same interface?
Section titled “What happens if two modules register the same interface?”DI last-registration-wins applies. The SG processes assemblies in a deterministic order, but the exact order depends on the dependency graph. If you need both implementations, use keyed services. If you need one shared implementation, put it in a shared project.
Can I use [StartupStep] without [Module]?
Section titled “Can I use [StartupStep] without [Module]?”Yes. Startup steps are independent of modules. A host project can have [StartupStep] classes without declaring a [Module]. However, without a module, there is no topology and the SG cannot auto-register infrastructure or map boundaries.
How do I disable a SG-generated default?
Section titled “How do I disable a SG-generated default?”Override it in the IPragmaticBuilder callback. DI last-registration-wins means your registration replaces the default:
await PragmaticApp.RunAsync(args, app =>{ // Replace the default IClock registration app.Services.AddSingleton<IClock, FakeClock>();});Why does my application enter maintenance mode?
Section titled “Why does my application enter maintenance mode?”Maintenance mode activates when startup fails and MaintenanceModeOptions.EnableOnStartupFailure is true (default). Check the /maintenance endpoint for error details. Common causes: missing configuration keys, database connection failures, DI resolution errors.
How do I add a custom middleware in the right place?
Section titled “How do I add a custom middleware in the right place?”Create an IStartupStep with the appropriate Order and add middleware in ConfigurePipeline. Refer to the order ranges: infrastructure (0-99), module (100-499), application (500+). For example, a request logging middleware should be at Order 80 (after routing but before authentication).
Can I run multiple IStartupSteps with the same Order?
Section titled “Can I run multiple IStartupSteps with the same Order?”Yes. Steps with the same Order are both executed, but their relative order to each other is non-deterministic. If the order between two steps matters, assign them different Order values.
How do I test without PragmaticApp.RunAsync?
Section titled “How do I test without PragmaticApp.RunAsync?”For integration tests, use WebApplicationFactory<T> as usual. The SG-generated PragmaticApp.RunAsync is the production entry point. In tests, you can override services via WebApplicationFactory.WithWebHostBuilder. The IStartupStep pattern works with test hosts because the generated code is just standard ASP.NET Core service registration.
Getting Help
Section titled “Getting Help”- GitHub Issues: github.com/nicola-pragmatic/Pragmatic.Design/issues
- Showcase Examples: See the
Showcaseproject for working composition across all modules. - Architecture Concepts: See concepts.md for the 3-Tier model and ecosystem integration.
- Service Registration: See service-registration.md for all registration patterns.
- Startup Pipeline: See startup-pipeline.md for step ordering and two-phase lifecycle.
- Remote Boundaries: See remote-boundaries.md for distributed deployment.