Skip to content

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

  1. Is the class decorated with [Service]? Without the attribute, the SG does not generate a registration. Add [Service] to the class.

  2. Does the class implement an interface? The default behavior registers the service as its first interface. If the class has no interfaces, PRAG1643 fires. Use [Service(AsSelf = true)] for concrete registration, or implement an interface.

  3. Is Pragmatic.Composition referenced in the library project? The SG only generates metadata when the package is referenced. Diagnostic PRAG1605 fires when [Service] is found without the Composition reference.

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

  5. Check the generated code. Open obj/Debug/net10.0/generated/ and look for _Infra.DI.ServiceRegistration.g.cs in the library project. Verify the service appears in the Add{Prefix}Services() method. In the host project, check Host.Services.g.cs for the aggregated registration.

  6. Is the service lifetime correct? A Transient service resolved via IServiceProvider (not a scope) will work, but a Scoped service 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.

  1. Does the module class have [Module]? The attribute is required for SG detection. Without it, the module is invisible.

  2. Does the host module have [Include<TModule>] or [Include<TModule, TDatabase>]? Library-level [Module] declares the module, but the host must explicitly include it.

  3. Is the module assembly referenced by the host project? Check the .csproj for a <ProjectReference> or <PackageReference> to the module’s assembly.

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

  5. Diagnostic PRAG1690 reports the count of discovered modules. If it says “Discovered 0 Pragmatic modules”, no [PragmaticMetadata] attributes were found in any referenced assembly.

  • The module project does not reference Pragmatic.Composition or Pragmatic.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.

Your IStartupStep implementation exists, but ConfigureServices or ConfigurePipeline is never called.

  1. Does the class have [StartupStep]? The attribute is required for discovery. Implementing IStartupStep alone is not enough.

  2. Does the class implement IStartupStep? The attribute without the interface triggers PRAG1630. Both are required.

  3. 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.
  4. Check the Order value. Steps with the same Order execute in an undefined order relative to each other. If your step depends on another step’s services, ensure your Order is higher.

  5. Diagnostic PRAG1694 fires when no [StartupStep] registrations are found. If this appears, no steps are being discovered from any referenced assembly.


Authentication fails, CORS headers are missing, or middleware behaves unexpectedly.

  1. Check the step Order values. ConfigurePipeline calls execute in ascending Order. Authentication (typically Order 100) must come before authorization (110). Routing (50) must come before endpoint-dependent middleware.

  2. Review the effective pipeline. For Debug builds, check Host.Topology.g.cs for the ordered list of steps. The generated ConfigurePipeline method in Host.Services.g.cs shows the exact call sequence.

  3. Built-in steps have fixed orders: ResponseCompression (25), Routing (50), CORS (75). Your steps must work around these.

  4. Multiple steps with the same Order. This is valid but the relative order between them is non-deterministic. If order matters, assign different Order values.

SymptomCauseFix
401 on all requestsAuth middleware after endpoint mappingSet auth step Order < endpoint mapping
CORS headers missingCORS step after routingUse built-in CorsStep (Order 75)
Tenant ID always nullTenant resolution before authenticationSet tenant step Order > auth step
Response not compressedCompression after response startedUse 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.

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

  2. Is the SG analyzer configured? The host project must have:

    <ProjectReference Include="..\Pragmatic.SourceGenerator\...\Pragmatic.SourceGenerator.csproj"
    OutputItemType="Analyzer"
    ReferenceOutputAssembly="false" />
  3. Check Host.Services.g.cs. Look for RegisterAllPragmaticServices. It should contain calls like services.AddPragmaticMultiTenancy() for each detected module.

  4. Verify FeatureDetector detection. The SG detects packages by checking for well-known types via GetTypeByMetadataName(). If the type is not found (e.g., the package API changed), detection fails silently.

  5. Clean and rebuild. SG output is cached aggressively. After adding a new package reference, a clean rebuild ensures the SG re-evaluates all features.


The application crashes or enters maintenance mode on startup.

  1. Check /maintenance endpoint. If maintenance mode is enabled (default), the application serves diagnostic info at /maintenance instead of crashing. Navigate to http://localhost:{port}/maintenance for error details.

  2. 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.json or environment variables.

  3. Database connection failure. If UseDatabaseEnsureCreated() or UseDatabaseMigrate() is enabled, a database connection error at startup will trigger maintenance mode. Verify the connection string and database availability.

  4. Circular dependency in DI. If a service’s constructor creates a circular chain (A -> B -> C -> A), the DI container throws at resolution time. Check PRAG1621 for [Inject]-based circular references detected at compile time. Constructor-based circularity is only caught at runtime.

  5. 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 registration
    • SqlException: Cannot open database — wrong connection string
    • ArgumentException: An item with the same key has already been added — duplicate keyed service

Services from one module are accidentally resolving services from another module’s boundary.

  1. Missing [BelongsTo]. Without boundary annotation, types float freely and may be registered in the wrong context.

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

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

Modules should communicate through action invokers (IDomainActionInvoker<T>), not direct service references. This ensures boundary isolation and enables future distribution via [RemoteBoundary].


A Pragmatic package is referenced but the SG does not detect it.

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

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

  3. SG not updated. If you updated the runtime package but not the SG, the SG may not know about new feature markers. Keep Pragmatic.SourceGenerator version aligned with runtime packages.


All Composition diagnostics use the PRAG16xx range:

IDSeverityCategoryDescription
PRAG1050ErrorPackageDuplicate [UsePackage<T>] on module
PRAG1600ErrorTopologyModule requires unavailable middleware
PRAG1601ErrorTopologyModule dependency not declared
PRAG1602ErrorTopologyCircular dependency detected
PRAG1605WarningReference[Service] without Pragmatic.Composition reference
PRAG1606ErrorReference[Decorator] requires Pragmatic.Composition reference
PRAG1610ErrorSchemaIncompatible metadata schema version
PRAG1611WarningSchemaNewer metadata schema version than supported
PRAG1612InfoSchemaLegacy metadata schema version (backward compat)
PRAG1620ErrorInjection[Inject] references unregistered service
PRAG1621ErrorInjection[Inject] creates circular reference
PRAG1630ErrorStartup[StartupStep] must implement IStartupStep
PRAG1631ErrorStartup[StartupStep] must be on a class
PRAG1632ErrorStartup[NeedsStep<T>] references unavailable step type
PRAG1640ErrorService[Service] requires a class type
PRAG1641WarningServiceDependency not registered
PRAG1642WarningServiceSingleton depends on scoped service (captive dependency)
PRAG1643WarningServiceNo interface found for service
PRAG1644InfoServiceService registered (verbose)
PRAG1645ErrorServiceAbstract class cannot be a service
PRAG1646WarningServiceKeyed services require .NET 8+
PRAG1651WarningDatabaseBoundary has no database configured
PRAG1652ErrorDatabaseDbContext name collision across different databases
PRAG1660ErrorDecoratorDecorator must implement at least one interface
PRAG1661ErrorDecoratorDecorator missing inner service constructor parameter
PRAG1670ErrorEvents[EventHandler] on class not implementing IDomainEventHandler<T>
PRAG1680WarningPackage[ExposeEndpoint<T>] references non-package action
PRAG1685ErrorRemote[RemoteBoundary<T>] and [Include<T>] on same module
PRAG1686WarningRemote[RemoteBoundary<T>] module has no actions
PRAG1687InfoRemote[RemoteBoundary<T>] base URL not configured
PRAG1690InfoDiscoveryDiscovered Pragmatic modules count
PRAG1691InfoDiscoveryModule composition info
PRAG1693InfoDiscoveryNo [Service] or [Decorator] registrations found
PRAG1694InfoDiscoveryNo [StartupStep] registrations found

The build fails with errors in SG-generated files (Host.Entry.g.cs or Host.Services.g.cs).

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

  2. Is the SG analyzer reference correct? The .csproj must 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.

  3. Check for namespace conflicts. If your project defines a type named PragmaticApp or PragmaticHost, it collides with the generated types. The SG generates into the Pragmatic.Composition.Hosting namespace for entry points and into the host’s root namespace for PragmaticHost.

  4. Clean and rebuild. SG caching can produce stale output. Run dotnet clean followed by dotnet build to force a full regeneration.


Actions invoked via [RemoteBoundary] return RemoteError instead of the expected business error.

  1. 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" }
    }
    }
    }
  2. Is the action registered on the remote host? The remote host must include the module locally ([Include<BillingModule, BillingDatabase>]). Without it, the /_pragmatic/invoke endpoint cannot dispatch the action.

  3. Check the RemoteError.Code. The error code indicates the failure point:

    CodeMeaningFix
    REMOTE_INVOKE_FAILEDHTTP call failedCheck network, URL, firewall
    REMOTE_DESERIALIZE_FAILEDResponse could not be deserializedCheck JSON serialization compatibility
    REMOTE_UNKNOWN_ERRORError with no ProblemDetailsCheck remote host logs
    REMOTE_ERRORBusiness error from remoteError propagated correctly; inspect the Title/Description
  4. 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.

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.

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.

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.