The Scoped lifetime ensures a single instance of a dependency within a scope — a typical example is a single DbContext, unit of work, or request context per web request. This example wraps scope creation in a Scope class: each scope gets its own RequestContext, all services resolved within that scope share it, and disposing the scope disposes all scoped instances it created.
using Shouldly;
using Pure.DI;
using static Pure.DI.Lifetime;
var composition = new Composition();
IRequestContext ctx1;
IRequestContext ctx2;
// Scope #1
using (var scope1 = composition.NewScope)
{
var checkout11 = scope1.Checkout;
var checkout12 = scope1.Checkout;
ctx1 = checkout11.Context;
// Same request => same scoped instance
ctx1.ShouldBe(checkout12.Context);
ctx1.IsDisposed.ShouldBeFalse();
}
// End of request #1 => scoped instance is disposed
ctx1.IsDisposed.ShouldBeTrue();
// Request #2
using (var scope1 = composition.NewScope)
{
var checkout2 = scope1.Checkout;
ctx2 = checkout2.Context;
}
// Different request => different scoped instance
ctx1.ShouldNotBe(ctx2);
// End of request #2 => scoped instance is disposed
ctx2.IsDisposed.ShouldBeTrue();
interface IIdGenerator
{
Guid Generate();
}
class IdGenerator : IIdGenerator
{
public Guid Generate() => Guid.NewGuid();
}
interface IRequestContext
{
Guid CorrelationId { get; }
bool IsDisposed { get; }
}
// Typically: DbContext / UnitOfWork / RequestTelemetry / Activity, etc.
sealed class RequestContext(IIdGenerator idGenerator)
: IRequestContext, IDisposable
{
public Guid CorrelationId { get; } = idGenerator.Generate();
public bool IsDisposed { get; private set; }
public void Dispose() => IsDisposed = true;
}
interface ICheckoutService
{
IRequestContext Context { get; }
}
// "Controller/service" that participates in request processing.
// It depends on a scoped context (per-request resource).
sealed class CheckoutService(IRequestContext context)
: ICheckoutService
{
public IRequestContext Context => context;
}
// Represents a scope
class Scope(Composition composition): IDisposable
{
private readonly Composition _scope = Composition.SetupScope(composition, new Composition());
public ICheckoutService Checkout => _scope.RequestRoot;
public void Dispose() => _scope.Dispose();
}
partial class Composition
{
static void Setup() =>
DI.Setup()
.Hint(Hint.ScopeMethodName, "SetupScope")
// Per-request lifetime
.Bind().As(Scoped).To<RequestContext>()
.Bind().As(Singleton).To<IdGenerator>()
// Regular service that consumes scoped context
.Bind().To<CheckoutService>()
// "Request root" (what your controller/handler resolves)
.Root<ICheckoutService>("RequestRoot")
.Root<Scope>("NewScope");
}Running this code sample locally
- Make sure you have the .NET SDK 10.0 or later installed
dotnet --list-sdk- Create a net10.0 (or later) console application
dotnet new console -n Sampledotnet add package Pure.DI
dotnet add package Shouldly- Copy the example code into the Program.cs file
You are ready to run the example 🚀
dotnet runNote
A scope is just another composition instance bound to its parent, so singletons remain shared across scopes while scoped instances are unique per scope.
The following partial class will be generated
partial class Composition: IDisposable
{
private Composition _root;
#if NET9_0_OR_GREATER
private Lock _lock = new Lock();
#else
private Object _lock = new Object();
#endif
private object[] _disposables = new object[1];
private int _disposeIndex;
private RequestContext? _singletonCompositionInOtherProject;
private IdGenerator? _singletonCompositionWithGenericRootsAndArgsInOtherProject;
internal static Composition SetupScope(Composition parentScope, Composition childScope)
{
if (Object.ReferenceEquals(parentScope, null)) throw new ArgumentNullException(nameof(parentScope));
if (Object.ReferenceEquals(childScope, null)) throw new ArgumentNullException(nameof(childScope));
if (Object.ReferenceEquals(parentScope, childScope)) throw new ArgumentException("The parent and child scopes must be different instances.", nameof(childScope));
childScope._root = parentScope._root ?? parentScope;
childScope._lock = parentScope._lock;
childScope._disposables = new object[1];
return childScope;
}
public ICheckoutService RequestRoot
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var root = _root ?? this;
if (_singletonCompositionInOtherProject is null)
lock (_lock)
if (_singletonCompositionInOtherProject is null)
{
if (root._singletonCompositionWithGenericRootsAndArgsInOtherProject is null)
{
root._singletonCompositionWithGenericRootsAndArgsInOtherProject = new IdGenerator();
}
_singletonCompositionInOtherProject = new RequestContext(root._singletonCompositionWithGenericRootsAndArgsInOtherProject);
_disposables[_disposeIndex++] = _singletonCompositionInOtherProject;
}
return new CheckoutService(_singletonCompositionInOtherProject);
}
}
public Scope NewScope
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new Scope(this);
}
}
public void Dispose()
{
int disposeIndex;
object[] disposables;
lock (_lock)
{
disposeIndex = _disposeIndex;
_disposeIndex = 0;
disposables = _disposables;
_disposables = new object[1];
_singletonCompositionInOtherProject = null;
_singletonCompositionWithGenericRootsAndArgsInOtherProject = null;
}
while (disposeIndex-- > 0)
{
switch (disposables[disposeIndex])
{
case IDisposable disposableInstance:
try
{
disposableInstance.Dispose();
}
catch (Exception exception)
{
OnDisposeException(disposableInstance, exception);
}
break;
}
}
}
partial void OnDisposeException<T>(T disposableInstance, Exception exception) where T : IDisposable;
}Class diagram:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Composition --|> IDisposable
RequestContext --|> IRequestContext
IdGenerator --|> IIdGenerator
CheckoutService --|> ICheckoutService
Composition ..> Scope : Scope NewScope
Composition ..> CheckoutService : ICheckoutService RequestRoot
RequestContext o-- "Singleton" IdGenerator : IIdGenerator
CheckoutService o-- "Scoped" RequestContext : IRequestContext
Scope *-- Composition : Composition
namespace Pure.DI.UsageTests.Lifetimes.ScopeScenario {
class CheckoutService {
<<class>>
+CheckoutService(IRequestContext context)
}
class Composition {
<<partial>>
+Scope NewScope
+ICheckoutService RequestRoot
}
class ICheckoutService {
<<interface>>
}
class IdGenerator {
<<class>>
+IdGenerator()
}
class IIdGenerator {
<<interface>>
}
class IRequestContext {
<<interface>>
}
class RequestContext {
<<class>>
+RequestContext(IIdGenerator idGenerator)
}
class Scope {
<<class>>
+Scope(Composition composition)
}
}
namespace System {
class IDisposable {
<<interface>>
}
}
See also: