Summary
The dependency injection documentation does not clearly warn that AddScoped has no natural scope boundary in .NET MAUI (for non-Blazor apps). Developers with ASP.NET Core or Blazor experience expect AddScoped to provide per-request or per-page instances, but in MAUI there is no built-in scope — a scoped service registered this way effectively behaves like a singleton within the app lifetime unless you manually create and manage IServiceScope.
Why it matters
This is a subtle but impactful correctness bug:
- In ASP.NET Core,
AddScoped creates one instance per HTTP request
- In Blazor,
AddScoped creates one instance per circuit
- In .NET MAUI (non-Blazor), there is no equivalent built-in scope boundary — a scoped service resolves to the same instance across the entire app unless the developer explicitly creates a scope with
IServiceScopeFactory
A developer who registers a unit-of-work or database context as AddScoped expecting per-navigation isolation will get a shared instance instead, leading to stale state, cross-page data contamination, and hard-to-reproduce bugs.
What should be documented
Add a note to the service lifetime table and/or the AddScoped entry in the dependency injection docs:
Note for .NET MAUI (non-Blazor) apps: AddScoped has no natural scope boundary. Unlike ASP.NET Core (per-request) or Blazor (per-circuit), MAUI does not automatically create scopes during navigation. A scoped service will behave like a singleton for the lifetime of the app unless you explicitly create a scope using IServiceScopeFactory.CreateScope(). For per-navigation isolation, prefer AddTransient, or manually manage scopes:
// Explicitly create a scope (advanced pattern)
using var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
await unitOfWork.SaveChangesAsync();
// scope is disposed here, releasing scoped services
For most MAUI use cases:
- Use
AddTransient for ViewModels and Pages (fresh instance per navigation)
- Use
AddSingleton for shared services (database connections, settings, caches)
- Reserve
AddScoped for manually managed scope patterns only
Suggested location
docs/fundamentals/dependency-injection.md — in the service lifetime table or as a callout after the lifetime guidance section
Summary
The dependency injection documentation does not clearly warn that
AddScopedhas no natural scope boundary in .NET MAUI (for non-Blazor apps). Developers with ASP.NET Core or Blazor experience expectAddScopedto provide per-request or per-page instances, but in MAUI there is no built-in scope — a scoped service registered this way effectively behaves like a singleton within the app lifetime unless you manually create and manageIServiceScope.Why it matters
This is a subtle but impactful correctness bug:
AddScopedcreates one instance per HTTP requestAddScopedcreates one instance per circuitIServiceScopeFactoryA developer who registers a unit-of-work or database context as
AddScopedexpecting per-navigation isolation will get a shared instance instead, leading to stale state, cross-page data contamination, and hard-to-reproduce bugs.What should be documented
Add a note to the service lifetime table and/or the
AddScopedentry in the dependency injection docs:For most MAUI use cases:
AddTransientfor ViewModels and Pages (fresh instance per navigation)AddSingletonfor shared services (database connections, settings, caches)AddScopedfor manually managed scope patterns onlySuggested location
docs/fundamentals/dependency-injection.md— in the service lifetime table or as a callout after the lifetime guidance section