liveBook · Manning

Captive dependency

As explained in ASP.NET Core in Action, 3e

A captive dependency is a lifetime mismatch in which a longer-lived service holds a dependency whose lifetime is shorter than its own.

A captive dependency occurs when a longer-lived service holds a shorter-lived dependency. The dependency becomes trapped in the longer-lived service and survives longer than intended. section 9.4.4

A singleton captures a scoped service

Registering DataContext as scoped and Repository as singleton creates a captive dependency. Repository lives for the application lifetime, so it keeps the DataContext created for its first scope instead of receiving a new instance for each request. This can produce subtle, difficult-to-find bugs. section 9.4.4

Match dependency lifetimes using this rule:

  • A singleton may depend only on singleton services. section 9.4.4
  • A scoped service may depend on scoped or singleton services. section 9.4.4
  • A transient service may depend on services with any lifetime. section 9.4.4
Figure 9.10 DataContext is registered as a scoped dependency, but Repository is a singleton. Even though you expect a new DataContext for every request, Repository captures the injected DataContext and causes it to be reused for the lifetime of the app.
CH09_F10_Lock3

Trace how a scoped DataContext is captured by a singleton Repository and reused across application requests. section 9.4.4

ASP.NET Core can validate scopes and throw an exception when it creates a service with a captive dependency. Scope validation is enabled by default in development; ValidateScopes and ValidateOnBuild can be enabled explicitly for broader checking. section 9.4.4

The same restriction applies when OptionsBuilder configuration retrieves services from dependency injection: Configure<TDeps> should use singleton services, not scoped services such as DbContext. section 31.2

How ASP.NET Core in Action, 3e covers it

How deep it goes
Defines lifetime capture, demonstrates request-level reuse, and explains validation, but stops short of alternative workaround designs.
Examples
Uses scoped DataContext inside singleton Repository, ValidateScopes configuration, and OptionsBuilder configuration attempting to inject DbContext.
What it assumes
Builds on dependency-injection registration and singleton, scoped, and transient service lifetimes.
What you can do after
Diagnose/debug lifetime-registration bugs using scope validation and lifetime compatibility rules.
How it connects
Connects dependency injection with service lifetime, singleton service, scoped service, transient service, and the options pattern.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of ASP.NET Core in Action, 3e. The section links point to the parts of the book it draws on.