Skip to main content

Dependency Injection Patterns in .NET

Master dependency injection in .NET — service lifetimes, factory patterns, keyed services, decorator pattern, and testing with DI.

Fill in the placeholders

Edit the values, then copy your finished prompt.

Your Prompt
prompt.txt
Design a robust dependency injection architecture for my .NET 8 application. The project is a ASP.NET Core Web API with background services with 30+ services. 1) Service registration: organize DI registration using extension methods per feature folders (Orders, Products, Users, Notifications) — show the builder.Services pattern for each module. 2) Lifetime management: explain Transient, Scoped, and Singleton with concrete examples from DbContext (scoped), HttpClient (singleton via factory), validators (transient) — show common mistakes (captive dependency, scoped in singleton). 3) Factory pattern: implement IServiceProvider-based factories for creating different payment processors based on payment method type where the concrete type is determined at runtime. 4) Keyed services (.NET 8): use keyed DI for multiple notification senders (email, SMS, push) selected by channel name instead of the factory pattern — compare readability and performance. 5) Decorator pattern: implement the decorator pattern using Scrutor or manual registration for adding caching and logging to repository classes without modifying them — add cross-cutting concerns without modifying original classes. 6) Options pattern: configure Jwt, ConnectionStrings, RateLimiting, EmailSettings using IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T> — explain when each is appropriate. 7) Testing: show how to override DI registrations in integration tests using WebApplicationFactory for overriding the live payment gateway with a fake in API tests. 8) Validation: add startup validation that fails fast if required services are missing or misconfigured.

What this prompt does

This prompt designs a dependency injection architecture for a [dotnet_version] application — typically a [project_type] with [service_count] services. It organizes registration into extension methods per [module_structure], explains Transient, Scoped, and Singleton lifetimes with examples from [lifetime_examples], implements a factory for [factory_scenario], uses .NET 8 keyed services for [keyed_scenario], applies the decorator pattern to [decorator_scenario], configures the options pattern for [config_sections], and adds startup validation that fails fast.

The structure works because the lifetime and validation steps map straight to real failures. Captive-dependency bugs and misused lifetimes — like a scoped DbContext injected into a singleton — are exactly the kind of thing that surfaces in production, so the prompt calls them out explicitly. Keyed services and the options pattern are what keep registration clean as the app grows, replacing hand-rolled factories and scattered configuration reads with patterns the framework supports directly.

When to use it

  • You are wiring up a new .NET API and want DI organized per [module_structure] from the start
  • You inherited an app with tangled registration and suspect lifetime misuse
  • You hit a captive-dependency bug and need the lifetimes in [lifetime_examples] straightened out
  • You need a factory or keyed services for runtime-selected implementations like [keyed_scenario]
  • You want cross-cutting concerns added via the decorator pattern for [decorator_scenario]
  • You want startup validation so missing or misconfigured services fail fast instead of at runtime

Example output

Expect an architecture walkthrough with code: per-module registration extension methods, lifetime explanations with concrete examples and common-mistake call-outs, factory implementation for runtime type selection, keyed-service registration compared against the factory approach, decorator-pattern setup via Scrutor or manual registration, options-pattern configuration showing IOptions, IOptionsSnapshot, and IOptionsMonitor, integration-test override examples, and fail-fast startup validation.

Pro tips

  • Watch lifetimes carefully — the classic captive-dependency bug is a scoped service trapped inside a singleton, and naming your real [lifetime_examples] helps the model flag it
  • Reach for keyed services in [keyed_scenario] before hand-rolling a factory; selecting a notification sender by channel name is cleaner than a switch inside a factory class
  • Choose between IOptions, IOptionsSnapshot, and IOptionsMonitor by reload needs — use IOptionsMonitor when config can change at runtime, IOptions when it can't
  • Use the decorator pattern for [decorator_scenario] to add caching or logging without touching the original class, keeping cross-cutting concerns separate
  • Add the startup validation step so a missing registration fails at boot, not on the first request that needs it
  • Keep registration in per-module extension methods so [module_structure] stays readable as [service_count] grows
  • Register HttpClient through the factory rather than as a raw singleton, since a long-lived client can hold stale DNS and exhaust sockets under load
  • Apply the decorator pattern via Scrutor for [decorator_scenario] so caching and logging wrap the real implementation without the consuming code knowing or caring

Frequently Asked Questions

What is a captive dependency and how does this prevent it?
A captive dependency occurs when a longer-lived service, like a singleton, holds a reference to a shorter-lived one, like a scoped DbContext, keeping it alive incorrectly. The prompt explains this with examples from `[lifetime_examples]` and adds fail-fast startup validation, which helps surface lifetime mismatches at boot rather than as subtle runtime bugs.
When should I use keyed services instead of a factory?
Use keyed services, available in .NET 8, when you select an implementation by a known key like a channel name in `[keyed_scenario]`. They are more readable than a hand-rolled factory for that case. Keep the factory pattern for when the concrete type depends on runtime logic that a simple key cannot express.
Which options interface should I choose for configuration?
Choose by reload behavior. Use `IOptions<T>` for configuration fixed at startup, `IOptionsSnapshot<T>` for per-request values in scoped contexts, and `IOptionsMonitor<T>` when configuration can change at runtime and you need change notifications. The prompt configures your `[config_sections]` and explains when each fits.
Can it help me override services in integration tests?
Yes. It shows how to override DI registrations using `WebApplicationFactory` for scenarios like `[test_scenario]`, such as swapping a live payment gateway for a fake. This lets your integration tests exercise real message and request flow while replacing only the external dependencies you do not want to call during testing.
Engr Mejba Ahmed

Need this built for real?

Engr Mejba Ahmed

AI Developer · Software Engineer

I'm Mejba — I design and ship production AI systems, automations, and full-stack apps. If you want this turned into a working solution for your team, let's talk.

More in C# & .NET Prompts

Engr Mejba Ahmed

Engr Mejba Ahmed

AI assistant · trained on my work

👋

Hey there!

Quick Actions

WhatsApp Direct line to me

Chat on WhatsApp

+880 1723 741224 · Replies within the hour on working days

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

mejba.13@gmail.com

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support