ASP.NET Core / Editorial solution guide
Resolve Dependency Registration and Scope Failures
Identify the actual service graph and ownership boundary before changing lifetimes or building another container.
Read-only knowledge base · No public comments
Recognize the symptoms
Startup or request execution reports an unresolved service, a scoped service is consumed by a singleton, or work later throws ObjectDisposedException. These symptoms belong to registration and lifetime contracts, not a universal need to make everything singleton. The exception text can name the failing dependency chain; preserve it without exposing secret configuration.
Understand the likely causes
- The interface/concrete registration may be absent or mismatched, a longer-lived owner may capture shorter-lived state, or a task may continue after its scope is disposed. An extra BuildServiceProvider during registration can create another ownership graph. A service successfully resolving once does not establish correct sharing, thread safety, or cleanup.
1. Record the requested type and consuming lifetime
Read the first relevant exception and identify the requested service type, its consumer, and whether the failure is at provider construction, resolution, or later use. Compare the exact interface/generic/named registration expected by the constructor. Work from a small synthetic graph; do not inspect or restart an existing configured application to test a guess.
2. Map sharing and disposal responsibility
Classify singleton, scoped, transient, factory-created, and already-constructed instances under the container rules. Determine who owns disposal and how long each borrower uses the object. A scoped object can still face concurrent callers inside one request. Capturing HttpContext or DbContext in a singleton/background task is a separate design problem from a missing registration.
3. Verify the intended registration in an isolated scope
The complete program registers a synthetic reader interface and resolves it in an explicit owned scope with relevant provider validation enabled. It demonstrates a correct local graph and useful result. It opens no database and does not validate the application’s host startup or real repository implementation. Use this pattern of evidence to isolate the graph before proposing an application change.
using System;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddScoped<IReportReader, SyntheticReader>();
using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true, ValidateOnBuild = true });
using var scope = provider.CreateScope();
int result = scope.ServiceProvider.GetRequiredService<IReportReader>().Read();
Console.WriteLine($"read={result}");
internal interface IReportReader { int Read(); }
internal sealed class SyntheticReader : IReportReader { public int Read() => 7; }
read=7
The explicit scope resolves the registered reader and returns 7; no configured service or provider executes.
4. Give long-lived work a bounded scope or factory
If a singleton worker needs scoped work, create a valid scope/factory resource for a bounded operation, await its users, then dispose it. Do not merely change DbContext to singleton or resolve it from the root. Separate contexts for parallel operations when the intended provider design requires it. Cancellation requests still need observed completion before release.
5. Remove duplicate ownership and hidden service location
Avoid building a second provider during registration, retaining disposable transients indefinitely, or disposing borrowed container dependencies inside a method. Keep registrations at the composition root and dependencies explicit. If a factory creates a resource, document who releases it. Correcting registration without correcting the later task/disposal boundary can leave the original incident only partly repaired.
Verify the fix
The complete local program must print read=7 and exit successfully. Test same-scope identity, cross-scope independence, disposal, invalid graphs, and after-suspension use as appropriate. Then verify the actual application registration and host/request path in an authorized isolated integration setup. A passing small container does not certify EF behavior, endpoint mapping, or all production lifetimes.
Keep it from recurring
- Keep lifetime diagrams, constructor/handler dependencies, scope validation, and ownership tests. Review background services and factories whenever dependencies change. Preserve async completion before disposal and avoid root/global service-location shortcuts. Fix the intended graph rather than weakening validation just to make startup appear green.