Skip to content

Commit bb8a78d

Browse files
Update README.md
1 parent fd8603d commit bb8a78d

26 files changed

Lines changed: 1108 additions & 175 deletions

AGENTS.md

Lines changed: 233 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,6 +2471,236 @@ To run the above code, the following NuGet packages must be added:
24712471
24722472
## Scope
24732473

2474+
Demonstrates scoped lifetime with `Hint(Hint.ScopeFactory, "on")` where scopes are represented by generated `Scope` objects created via `CreateScope()`.
2475+
2476+
```c#
2477+
using Shouldly;
2478+
using Pure.DI;
2479+
using static Pure.DI.Lifetime;
2480+
2481+
var composition = new Composition(desc: "Checkout");
2482+
IRequestContext ctx1;
2483+
IRequestContext ctx2;
2484+
2485+
// Scope #1
2486+
using (var scope1 = composition.NewScope)
2487+
{
2488+
var checkout11 = scope1.Checkout;
2489+
var checkout12 = scope1.Checkout;
2490+
ctx1 = checkout11.Context;
2491+
2492+
// Same request => same scoped instance
2493+
ctx1.ShouldBe(checkout12.Context);
2494+
ctx1.IsDisposed.ShouldBeFalse();
2495+
}
2496+
2497+
// End of request #1 => scoped instance is disposed
2498+
ctx1.IsDisposed.ShouldBeTrue();
2499+
2500+
// Request #2
2501+
using (var scope1 = composition.NewScope)
2502+
{
2503+
var checkout2 = scope1.Checkout;
2504+
ctx2 = checkout2.Context;
2505+
}
2506+
2507+
// Different request => different scoped instance
2508+
ctx1.ShouldNotBe(ctx2);
2509+
2510+
// End of request #2 => scoped instance is disposed
2511+
ctx2.IsDisposed.ShouldBeTrue();
2512+
2513+
interface IIdGenerator
2514+
{
2515+
Guid Generate();
2516+
}
2517+
2518+
class IdGenerator : IIdGenerator
2519+
{
2520+
public Guid Generate() => Guid.NewGuid();
2521+
}
2522+
2523+
interface IRequestContext
2524+
{
2525+
Guid CorrelationId { get; }
2526+
2527+
bool IsDisposed { get; }
2528+
}
2529+
2530+
// Typically: DbContext / UnitOfWork / RequestTelemetry / Activity, etc.
2531+
sealed class RequestContext(IIdGenerator idGenerator)
2532+
: IRequestContext, IDisposable
2533+
{
2534+
public Guid CorrelationId { get; } = idGenerator.Generate();
2535+
2536+
public bool IsDisposed { get; private set; }
2537+
2538+
public void Dispose() => IsDisposed = true;
2539+
}
2540+
2541+
interface ICheckoutService
2542+
{
2543+
IRequestContext Context { get; }
2544+
}
2545+
2546+
// "Controller/service" that participates in request processing.
2547+
// It depends on a scoped context (per-request resource).
2548+
sealed class CheckoutService(
2549+
string description,
2550+
IRequestContext context)
2551+
: ICheckoutService
2552+
{
2553+
public IRequestContext Context => context;
2554+
}
2555+
2556+
// Represents a scope
2557+
class Scope(Composition composition): IDisposable
2558+
{
2559+
private readonly Composition _scope = composition.CreateScope();
2560+
2561+
public ICheckoutService Checkout => _scope.RequestRoot;
2562+
2563+
public void Dispose() => _scope.Dispose();
2564+
}
2565+
2566+
partial class Composition
2567+
{
2568+
static void Setup() =>
2569+
2570+
DI.Setup()
2571+
.Hint(Hint.ScopeFactoryName, "CreateScope")
2572+
.Arg<string>("desc")
2573+
// Per-request lifetime
2574+
.Bind().As(Scoped).To<RequestContext>()
2575+
2576+
.Bind().As(Singleton).To<IdGenerator>()
2577+
2578+
// Regular service that consumes scoped context
2579+
.Bind().To<CheckoutService>()
2580+
2581+
// "Request root" (what your controller/handler resolves)
2582+
.Root<ICheckoutService>("RequestRoot")
2583+
.Root<Scope>("NewScope");
2584+
}
2585+
```
2586+
2587+
To run the above code, the following NuGet packages must be added:
2588+
- [Pure.DI](https://www.nuget.org/packages/Pure.DI)
2589+
- [Shouldly](https://www.nuget.org/packages/Shouldly)
2590+
2591+
>[!NOTE]
2592+
>This approach is useful when you need runtime scope creation without deriving a child composition type.
2593+
2594+
## Scope factory
2595+
2596+
Demonstrates scoped lifetime with `Hint(Hint.ScopeFactory, "on")` where scopes are represented by generated `Scope` objects created via `CreateScope()`.
2597+
2598+
```c#
2599+
using Shouldly;
2600+
using Pure.DI;
2601+
using static Pure.DI.Lifetime;
2602+
2603+
var composition = new Composition();
2604+
IRequestContext ctx1;
2605+
IRequestContext ctx2;
2606+
2607+
// Request #1
2608+
using (var request1 = composition.CreateScope())
2609+
{
2610+
var checkout11 = request1.RequestRoot;
2611+
var checkout12 = request1.RequestRoot;
2612+
ctx1 = checkout11.Context;
2613+
2614+
// Same request => same scoped instance
2615+
ctx1.ShouldBe(checkout12.Context);
2616+
ctx1.IsDisposed.ShouldBeFalse();
2617+
}
2618+
2619+
// End of request #1 => scoped instance is disposed
2620+
ctx1.IsDisposed.ShouldBeTrue();
2621+
2622+
// Request #2
2623+
using (var request2 = composition.CreateScope())
2624+
{
2625+
var checkout2 = request2.RequestRoot;
2626+
ctx2 = checkout2.Context;
2627+
}
2628+
2629+
// Different request => different scoped instance
2630+
ctx1.ShouldNotBe(ctx2);
2631+
2632+
// End of request #2 => scoped instance is disposed
2633+
ctx2.IsDisposed.ShouldBeTrue();
2634+
2635+
interface IIdGenerator
2636+
{
2637+
Guid Generate();
2638+
}
2639+
2640+
class IdGenerator : IIdGenerator
2641+
{
2642+
public Guid Generate() => Guid.NewGuid();
2643+
}
2644+
2645+
interface IRequestContext
2646+
{
2647+
Guid CorrelationId { get; }
2648+
2649+
bool IsDisposed { get; }
2650+
}
2651+
2652+
// Typically: DbContext / UnitOfWork / RequestTelemetry / Activity, etc.
2653+
sealed class RequestContext(IIdGenerator idGenerator)
2654+
: IRequestContext, IDisposable
2655+
{
2656+
public Guid CorrelationId { get; } = idGenerator.Generate();
2657+
2658+
public bool IsDisposed { get; private set; }
2659+
2660+
public void Dispose() => IsDisposed = true;
2661+
}
2662+
2663+
interface ICheckoutService
2664+
{
2665+
IRequestContext Context { get; }
2666+
}
2667+
2668+
// "Controller/service" that participates in request processing.
2669+
// It depends on a scoped context (per-request resource).
2670+
sealed class CheckoutService(IRequestContext context)
2671+
: ICheckoutService
2672+
{
2673+
public IRequestContext Context => context;
2674+
}
2675+
2676+
partial class Composition
2677+
{
2678+
static void Setup() =>
2679+
2680+
DI.Setup()
2681+
.Hint(Hint.ScopeFactoryName, "CreateScope")
2682+
// Per-request lifetime
2683+
.Bind().As(Scoped).To<RequestContext>()
2684+
2685+
.Bind().As(Singleton).To<IdGenerator>()
2686+
2687+
// Regular service that consumes scoped context
2688+
.Bind().To<CheckoutService>()
2689+
2690+
// "Request root" (what your controller/handler resolves)
2691+
.Root<ICheckoutService>("RequestRoot");
2692+
}
2693+
```
2694+
2695+
To run the above code, the following NuGet packages must be added:
2696+
- [Pure.DI](https://www.nuget.org/packages/Pure.DI)
2697+
- [Shouldly](https://www.nuget.org/packages/Shouldly)
2698+
2699+
>[!NOTE]
2700+
>This approach is useful when you need runtime scope creation without deriving a child composition type.
2701+
2702+
## Scoped
2703+
24742704
The `Scoped` lifetime ensures that there will be a single instance of the dependency for each scope.
24752705

24762706
```c#
@@ -7330,7 +7560,7 @@ class UserService(
73307560

73317561
public IUserRepository CloudRepository { get; } = cloudRepo;
73327562

7333-
public required IUserRepository BackupRepository { init; get; }
7563+
public required IUserRepository BackupRepository { get; init; }
73347564

73357565
public IUserRepository FetcherRepository => fetcher.Repository;
73367566
}
@@ -7441,7 +7671,7 @@ interface ICheckoutService
74417671

74427672
class CheckoutService : ICheckoutService
74437673
{
7444-
public required IPaymentGateway Gateway { init; get; }
7674+
public required IPaymentGateway Gateway { get; init; }
74457675
}
74467676
```
74477677

@@ -7574,7 +7804,7 @@ class SmartHomeSystem(
75747804

75757805
public ISensor Zone2 { get; } = zone2;
75767806

7577-
public required ISensor OutdoorSensor { init; get; }
7807+
public required ISensor OutdoorSensor { get; init; }
75787808

75797809
public ISensor ClimateSensor => climateControl.Sensor;
75807810
}

0 commit comments

Comments
 (0)