2. Configuration & Dependency Injection

ASP.NET Core Configuration & Dependency Injection - Complete Guide

Practical Guide + Technical Reference


📋 Table of Contents

Part 1: Practical Guide (Hands-On)

  1. Configuration Basics

  2. Configuration Sources & Priority

  3. Dependency Injection Fundamentals

  4. Service Lifetimes (Transient, Scoped, Singleton)

  5. Registering Services (3 Methods)

  6. Injecting and Using Services

  7. Common DI Patterns

  8. Troubleshooting Common Issues

  9. Best Practices

Part 2: Technical Reference (Deep Dive)

  1. Important Interfaces & Classes Reference

  2. Configuration Deep-Dive

  3. Advanced DI Topics

  4. Performance Considerations


PART 1: PRACTICAL GUIDE


1. Configuration Basics

Simple Definition: Configuration is how you store settings outside your code (database connections, API keys, feature flags).

Think of it like: A settings panel for your application that can change without recompiling code.

Why Configuration?

Key Benefits:

  • 🔧 Change settings without recompiling

  • 🌍 Different settings per environment (Dev, Staging, Prod)

  • 🔐 Keep secrets secure (API keys, passwords)

  • 📊 Easy to manage and audit


2. Configuration Sources & Priority

Configuration Sources (Loaded in Order)

ASP.NET Core loads configuration from multiple sources. Later sources override earlier ones.

Example: Same Key Different Sources

Environment Variable (highest priority):


Reading Configuration - 3 Methods

Method 1: IConfiguration Directly (Quick & Simple)

When to use:

  • ✅ Quick prototyping

  • ✅ Simple string values

  • ❌ Complex objects

  • ❌ Strong typing

Step 1: Inject IConfiguration

Complete Example:


Method 2: GetSection & Bind (Medium Complexity)

When to use:

  • ✅ Nested configurations

  • ✅ Arrays/lists

  • ✅ Some type safety

  • ⚠️ Manual binding

Step 1: Create Settings Class

Step 2: Configure appsettings.json

Step 3: Bind Configuration


When to use:

  • ✅ Production applications

  • ✅ Strong typing

  • ✅ Validation support

  • ✅ Multiple consumers

  • ✅ Dependency injection

Step 1: Create Options Class

Step 2: Configure appsettings.json

Step 3: Register in DI

Step 4: Inject and Use


Configuration Methods Comparison

Method
Complexity
Type Safety
DI Support
Reload
Production

config["Key"]

Simple

❌ None

✅ Yes

N/A

⚠️ Testing only

GetSection().Bind()

Medium

⚠️ Partial

⚠️ Manual

❌ No

⚠️ Maybe

IOptions<T>

Medium

✅ Full

✅ Yes

❌ No

✅ Yes

IOptionsSnapshot<T>

Medium

✅ Full

✅ Yes

✅ Per request

✅ Yes

IOptionsMonitor<T>

Complex

✅ Full

✅ Yes

✅ Live

✅ Advanced


3. Dependency Injection Fundamentals

Simple Definition: A design pattern where objects don't create their dependencies - they receive them instead.

Think of it like: Instead of building your own tools (hard), someone hands you the tools you need (easy).

Without DI (Tightly Coupled ❌)

Problems:

  • ❌ Hard to test (can't mock dependencies)

  • ❌ Tight coupling (changes ripple through code)

  • ❌ Hard to change implementations

  • ❌ Can't use interfaces


With DI (Loosely Coupled ✅)

Benefits:

  • ✅ Easy to test (inject mocks)

  • ✅ Loose coupling

  • ✅ Easy to swap implementations

  • ✅ Interface-based design


4. Service Lifetimes (Transient, Scoped, Singleton)

Visual Comparison


Transient Lifetime

Definition: New instance every time requested.

When to use:

  • ✅ Lightweight, stateless services

  • ✅ No shared state

  • ✅ Short-lived operations

  • ❌ Database contexts (use Scoped)

  • ❌ Expensive to create (use Singleton)

Example:


Scoped Lifetime ⭐ MOST COMMON

Definition: One instance per HTTP request (or scope).

When to use:

  • ✅ Database contexts (Entity Framework)

  • ✅ Unit of Work pattern

  • ✅ Per-request state

  • ✅ Most business logic services

Example:


Singleton Lifetime

Definition: One instance for entire application lifetime.

When to use:

  • ✅ Configuration/Settings

  • ✅ Caching services

  • ✅ Logging

  • ✅ Expensive to create objects

  • ❌ Database contexts (NOT thread-safe)

  • ⚠️ Must be thread-safe!

Example:


Lifetime Decision Tree


Service Lifetime Comparison Table

Lifetime
Instance Created
Shared Across
Use For
Thread Safety Required

Transient

Every injection

Nothing

Stateless services

No

Scoped

Once per request

Same request

DbContext, per-request state

No

Singleton

Once for app

All requests

Cache, config, logging

✅ YES


5. Registering Services (3 Methods)

Method 1: Built-in DI (Direct Registration)

When to use:

  • ✅ Simple projects

  • ✅ Few services

  • ✅ Quick prototypes

  • ❌ Large projects (gets messy)

Step 1: Register Services in Program.cs

Complete Example:


When to use:

  • ✅ Clean, organized code

  • ✅ Reusable service groups

  • ✅ Production applications

  • ✅ Library/package development

Step 1: Create Extension Method

Step 2: Use in Program.cs

Multiple Extension Methods (Best Practice):


Method 3: Third-Party Containers (Advanced)

When to use:

  • ✅ Need advanced features (property injection, decorators, interceptors)

  • ✅ Legacy code integration

  • ⚠️ Adds complexity

  • ⚠️ Another dependency

Popular Containers:

  • Autofac (most popular)

  • StructureMap

  • Castle Windsor

  • Ninject

Example with Autofac:

Step 1: Install Package

Step 2: Configure Autofac


6. Injecting and Using Services

Constructor Injection (Primary Method) ⭐

When to use: 99% of the time


FromServices Attribute (Action Injection)

When to use: Only needed in one action


Service Locator Pattern (AVOID! ❌)

Anti-pattern - Don't use unless absolutely necessary

Why it's bad:

  • ❌ Hides dependencies (hard to see what's needed)

  • ❌ Runtime errors instead of compile-time

  • ❌ Harder to test

  • ❌ Breaks dependency injection principles


Minimal API Injection


7. Common DI Patterns

Repository Pattern

Purpose: Abstract data access logic


Unit of Work Pattern

Purpose: Coordinate multiple repository operations in a transaction


Factory Pattern

Purpose: Create objects with complex initialization


8. Troubleshooting Common Issues

Problem 1: Service Not Registered

Error:

Solution:


Problem 2: Captive Dependency

Error: Singleton depends on Scoped service

Solution:


Problem 3: Circular Dependencies

Error:

Example:

Solution 1: Refactor design

Solution 2: Use Lazy


Problem 4: Missing Configuration Section

Error:

Solution:


9. Best Practices

✅ DO's

  1. Use constructor injection

  2. Program to interfaces

  3. Use appropriate lifetime

    • Transient: Stateless services

    • Scoped: DbContext, per-request state

    • Singleton: Cache, configuration

  4. Use extension methods for registration

  5. Use IOptions for configuration


❌ DON'Ts

  1. Don't inject IServiceProvider

  2. Don't create dependencies manually

  3. Don't inject Scoped into Singleton

  4. Don't hardcode configuration

  5. Don't forget to dispose


Best Practices Checklist

Configuration:

Dependency Injection:

Code Organization:


PART 2: TECHNICAL REFERENCE


10. Important Interfaces & Classes Reference

IConfiguration Interface

Namespace: Microsoft.Extensions.Configuration

Purpose: Read configuration from multiple sources

Declaration:

Common Members:

Member
Return Type
Description

this[string key]

string

Get/set value by key (e.g., "ConnectionStrings:Default")

GetSection(key)

IConfigurationSection

Get configuration section

GetChildren()

IEnumerable<IConfigurationSection>

Get child sections

GetReloadToken()

IChangeToken

Token that triggers when configuration changes

Usage Examples:


IConfigurationSection Interface

Namespace: Microsoft.Extensions.Configuration

Purpose: Represents a section of configuration

Declaration:

Additional Members:

Member
Return Type
Description

Key

string

Section key name

Path

string

Full path to this section

Value

string

Section value (if it's a value, not object)

Usage:


ConfigurationBuilder Class

Namespace: Microsoft.Extensions.Configuration

Purpose: Build configuration from multiple sources

Common Methods:

Method
Description

AddJsonFile(path, optional, reloadOnChange)

Add JSON file

AddXmlFile(path, optional, reloadOnChange)

Add XML file

AddIniFile(path, optional, reloadOnChange)

Add INI file

AddEnvironmentVariables()

Add environment variables

AddCommandLine(args)

Add command-line arguments

AddUserSecrets<T>()

Add user secrets (dev only)

AddInMemoryCollection()

Add in-memory collection

Build()

Build and return IConfiguration

Usage:


IServiceCollection Interface

Namespace: Microsoft.Extensions.DependencyInjection

Purpose: Register services for dependency injection

Common Extension Methods:

Method
Lifetime
Description

AddTransient<TService, TImplementation>()

Transient

New instance every time

AddTransient<TService>()

Transient

Same type as service and implementation

AddScoped<TService, TImplementation>()

Scoped

One instance per request

AddScoped<TService>()

Scoped

Same type as service and implementation

AddSingleton<TService, TImplementation>()

Singleton

One instance for app lifetime

AddSingleton<TService>()

Singleton

Same type as service and implementation

AddSingleton<TService>(instance)

Singleton

Use provided instance

Advanced Methods:


IServiceProvider Interface

Namespace: Microsoft.Extensions.DependencyInjection

Purpose: Resolve registered services

Common Methods:

Method
Description

GetService<T>()

Get service (returns null if not found)

GetRequiredService<T>()

Get service (throws if not found)

GetServices<T>()

Get all registered implementations

Usage:

Create Scope:


IOptions, IOptionsSnapshot, IOptionsMonitor

Namespace: Microsoft.Extensions.Options

Purpose: Access strongly-typed configuration

Comparison:

Interface
Lifetime
Reload
Use Case

IOptions<T>

Singleton

❌ No

Read once at startup

IOptionsSnapshot<T>

Scoped

✅ Per request

Per-request reload

IOptionsMonitor<T>

Singleton

✅ Live

Real-time configuration changes

IOptions - Most Common:

IOptionsSnapshot - Per Request:

IOptionsMonitor - Live Reload:


ILogger and ILoggerFactory

Namespace: Microsoft.Extensions.Logging

Purpose: Logging throughout application

ILogger Members:

Method
Description

LogTrace(message)

Detailed diagnostic information

LogDebug(message)

Debugging information

LogInformation(message)

General information

LogWarning(message)

Warning but application continues

LogError(exception, message)

Error occurred

LogCritical(exception, message)

Critical failure

Usage:

Structured Logging:


WebApplication and WebApplicationBuilder

Namespace: Microsoft.AspNetCore.Builder

WebApplicationBuilder Properties:

Property
Type
Description

Services

IServiceCollection

Register services

Configuration

ConfigurationManager

Access configuration

Environment

IWebHostEnvironment

Environment info

Host

IHostBuilder

Configure host

WebHost

IWebHostBuilder

Configure web host

Logging

ILoggingBuilder

Configure logging

WebApplication Properties:

Property
Type
Description

Services

IServiceProvider

Resolve services

Configuration

IConfiguration

Access configuration

Environment

IWebHostEnvironment

Environment info

Lifetime

IHostApplicationLifetime

Application lifetime events

Logger

ILogger

Logger for WebApplication

Urls

ICollection<string>

URLs to listen on

Usage:


11. Configuration Deep-Dive

Configuration Providers & Order

ASP.NET Core loads configuration from providers in this order (last wins):

  1. appsettings.json - Base configuration

  2. appsettings.{Environment}.json - Environment-specific

  3. User Secrets - Development secrets (never commit)

  4. Environment Variables - Server/container configuration

  5. Command-line Arguments - Runtime overrides

Example:

Environment Variable (highest priority):

Command-line (highest priority):


User Secrets (Development Only)

Purpose: Store secrets locally without committing to source control

Step 1: Initialize User Secrets

This adds to .csproj:

Step 2: Set Secrets

Step 3: List Secrets

Step 4: Use in Code

Location:

  • Windows: %APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json

  • Linux/macOS: ~/.microsoft/usersecrets/<UserSecretsId>/secrets.json


Environment Variables

Naming Convention: Use double underscore __ for nesting

Usage:


Azure Key Vault Integration

Step 1: Install Package

Step 2: Configure

Step 3: Use Secrets


Configuration Validation

Step 1: Add Validation Attributes

Step 2: Enable Validation

Step 3: Custom Validation


Complex Configuration Binding

Arrays:

Nested Objects:

Dictionaries:


12. Advanced DI Topics

Multiple Implementations

Register:

Use:


Keyed Services ✨ .NET 8.0+

Register with Keys:

Inject by Key:

Get Keyed Service from IServiceProvider:


Decorator Pattern

Scenario: Add logging/caching to existing service


Generic Type Registration

Register Generic Repository:


Conditional Registration

Register Based on Environment:

Register Based on Configuration:

TryAdd Methods:


Replacing Built-in Services

Replace Existing Service:


13. Performance Considerations

Service Lifetime Performance Impact

Transient:

  • ✅ No memory overhead between requests

  • ❌ More allocations (GC pressure)

  • ❌ Slower resolution

Scoped:

  • ✅ Balanced performance

  • ✅ Shared within request (less allocations)

  • ✅ Automatically disposed per request

Singleton:

  • ✅ Fastest resolution

  • ✅ No allocations after first creation

  • ⚠️ Memory stays for app lifetime

  • ⚠️ Must be thread-safe


Avoid Service Locator Anti-Pattern

Slow (Service Locator):

Fast (Constructor Injection):


Configuration Performance

Slow (Repeated Reads):

Fast (IOptions):


Summary: Complete Checklist

Configuration Checklist

Setup:

Structure:

Usage:


Dependency Injection Checklist

Registration:

Usage:

Organization:

Best Practices:


This completes the Configuration & Dependency Injection guide combining practical hands-on content with deep technical reference!

Last updated