Beginner to Advanced
Overview
Duration: 6-20 hours (can be split across multiple sessions)
Target Audience: .NET developers from beginner to advanced
Prerequisites: Basic C# knowledge, understanding of classes and interfaces
Module 1: Foundations
1.1 What is Dependency Injection?
Learning Objectives:
- Understand tight coupling problems
- Learn IoC and DI principles
- Recognize DI benefits
Theory
- Problems with tight coupling
- Inversion of Control (IoC) principle
- Dependency Injection pattern
- Benefits: testability, maintainability, flexibility
Hands-on Exercise 1: The Problem
// Before DI - Tightly coupled code
public class OrderService
{
private EmailService _emailService;
private PaymentService _paymentService;
public OrderService()
{
_emailService = new EmailService(); // Tight coupling
_paymentService = new PaymentService(); // Hard to test
}
public void ProcessOrder(Order order)
{
_paymentService.ProcessPayment(order.Amount);
_emailService.SendConfirmation(order.CustomerEmail);
}
}
Task: Students identify problems with this code and discuss testing challenges.
Hands-on Exercise 2: The Solution (1 hour)
// After DI - Loosely coupled code
public interface IEmailService
{
void SendConfirmation(string email);
}
public interface IPaymentService
{
void ProcessPayment(decimal amount);
}
public class OrderService
{
private readonly IEmailService _emailService;
private readonly IPaymentService _paymentService;
public OrderService(IEmailService emailService, IPaymentService paymentService)
{
_emailService = emailService;
_paymentService = paymentService;
}
public void ProcessOrder(Order order)
{
_paymentService.ProcessPayment(order.Amount);
_emailService.SendConfirmation(order.CustomerEmail);
}
}
Task: Refactor the tight coupling example to use DI.
Module 2: .NET 8 DI Container Basics
2.1 Built-in DI Container
Learning Objectives:
- Set up DI in .NET 8 applications
- Understand service registration
- Learn basic service lifetimes
Theory :
- .NET 8 built-in container overview
- Service registration methods
- Constructor injection basics
Hands-on Exercise 3: Console Application Setup
// Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
// Register services
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddTransient<IPaymentService, PaymentService>();
builder.Services.AddTransient<OrderService>();
var host = builder.Build();
// Use services
var orderService = host.Services.GetRequiredService<OrderService>();
var order = new Order { CustomerEmail = "test@example.com", Amount = 100 };
orderService.ProcessOrder(order);
Task: Create a complete console application using DI.
2.2 Service Lifetimes
Learning Objectives:
- Understand Transient, Scoped, Singleton
- Choose appropriate lifetimes
- Avoid lifetime pitfalls
Theory
- Transient: New instance every time
- Scoped: One instance per scope
- Singleton: One instance for application lifetime
Hands-on Exercise 4: Lifetime Comparison
public interface ICounterService
{
int GetCount();
}
public class CounterService : ICounterService
{
private static int _staticCount = 0;
private int _instanceCount = 0;
public CounterService()
{
_staticCount++;
_instanceCount++;
}
public int GetCount() => _instanceCount;
}
// Registration variations
builder.Services.AddTransient<ICounterService, CounterService>();
builder.Services.AddScoped<ICounterService, CounterService>();
builder.Services.AddSingleton<ICounterService, CounterService>();
Task: Test different lifetimes and observe behavior differences.
Module 3: Web API with DI
3.1 DI in ASP.NET Core
Learning Objectives:
- Implement DI in web applications
- Use DI with controllers
- Understand scoped lifetime in web context
Hands-on Exercise 5: Web API Project
// Services/IProductService.cs
public interface IProductService
{
Task<List<Product>> GetAllProductsAsync();
Task<Product> GetProductByIdAsync(int id);
Task<Product> CreateProductAsync(Product product);
}
// Controllers/ProductController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<ActionResult<List<Product>>> GetProducts()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products);
}
}
// Program.cs
builder.Services.AddScoped<IProductService, ProductService>();
Task: Build a complete Web API with multiple services and controllers.
3.2 Entity Framework Integration
Learning Objectives:
- Integrate EF Core with DI
- Understand DbContext lifetime
- Implement Repository pattern
Hands-on Exercise 6: Database Integration
// Data/ApplicationDbContext.cs
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
public DbSet<Product> Products { get; set; }
}
// Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IProductRepository, ProductRepository>();
Task: Implement a complete data access layer using EF Core and DI.
Module 4: Advanced DI Patterns
4.1 Multiple Implementations
Learning Objectives:
- Register multiple implementations
- Use factory patterns
- Implement strategy pattern with DI
Theory
- When to use multiple implementations
- Factory pattern with DI
- Strategy pattern implementation
Hands-on Exercise 7: Notification System
public interface INotificationService
{
Task SendNotificationAsync(string message, string recipient);
}
public class EmailNotificationService : INotificationService
{
public async Task SendNotificationAsync(string message, string recipient)
{
// Email implementation
await Task.Delay(100);
Console.WriteLine($"Email sent to {recipient}: {message}");
}
}
public class SmsNotificationService : INotificationService
{
public async Task SendNotificationAsync(string message, string recipient)
{
// SMS implementation
await Task.Delay(50);
Console.WriteLine($"SMS sent to {recipient}: {message}");
}
}
// Registration
builder.Services.AddScoped<EmailNotificationService>();
builder.Services.AddScoped<SmsNotificationService>();
builder.Services.AddScoped<IEnumerable<INotificationService>>(provider =>
new List<INotificationService>
{
provider.GetService<EmailNotificationService>(),
provider.GetService<SmsNotificationService>()
});
Task: Implement a notification system that can send via multiple channels.
4.2 Keyed Services (.NET 8 Feature)
Learning Objectives:
- Use keyed services for multiple implementations
- Understand when to use keyed services
- Implement service selection logic
Theory
- New keyed services feature in .NET 8
- Benefits over traditional approaches
- Registration and consumption patterns
Hands-on Exercise 8: Keyed Services Implementation
// Registration with keys
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
builder.Services.AddKeyedScoped<INotificationService, PushNotificationService>("push");
// Usage in services
public class NotificationOrchestrator
{
private readonly IServiceProvider _serviceProvider;
public NotificationOrchestrator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task SendNotificationAsync(string type, string message, string recipient)
{
var service = _serviceProvider.GetRequiredKeyedService<INotificationService>(type);
await service.SendNotificationAsync(message, recipient);
}
}
// Or using [FromKeyedServices] attribute
public class NotificationController : ControllerBase
{
public async Task<IActionResult> SendEmail(
[FromKeyedServices("email")] INotificationService emailService,
NotificationRequest request)
{
await emailService.SendNotificationAsync(request.Message, request.Recipient);
return Ok();
}
}
Task: Refactor the notification system to use keyed services.
4.3 Decorators and Interceptors
Learning Objectives:
- Implement decorator pattern with DI
- Add cross-cutting concerns
- Understand service interception
Hands-on Exercise 9: Logging Decorator
public class LoggingProductService : IProductService
{
private readonly IProductService _inner;
private readonly ILogger<LoggingProductService> _logger;
public LoggingProductService(IProductService inner, ILogger<LoggingProductService> logger)
{
_inner = inner;
_logger = logger;
}
public async Task<List<Product>> GetAllProductsAsync()
{
_logger.LogInformation("Getting all products");
var result = await _inner.GetAllProductsAsync();
_logger.LogInformation("Retrieved {Count} products", result.Count);
return result;
}
}
// Registration
builder.Services.AddScoped<ProductService>();
builder.Services.AddScoped<IProductService>(provider =>
{
var productService = provider.GetRequiredService<ProductService>();
var logger = provider.GetRequiredService<ILogger<LoggingProductService>>();
return new LoggingProductService(productService, logger);
});
Task: Implement logging and caching decorators.
Module 5: Configuration and Options
5.1 Options Pattern
Learning Objectives:
- Bind configuration to objects
- Use IOptions, IOptionsSnapshot, IOptionsMonitor
- Validate configuration
Theory
- Options pattern benefits
- Different option interfaces
- Configuration validation
Hands-on Exercise 10: Configuration Integration (1.5 hours)
// appsettings.json
{
"EmailSettings": {
"SmtpServer": "smtp.example.com",
"Port": 587,
"Username": "user@example.com",
"Password": "password"
}
}
// EmailSettings.cs
public class EmailSettings
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
// EmailService.cs
public class EmailService : IEmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}
public async Task SendEmailAsync(string to, string subject, string body)
{
// Use _settings to configure SMTP client
}
}
// Program.cs
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
builder.Services.AddScoped<IEmailService, EmailService>();
Task: Implement configuration-driven services with validation.
Module 6: Testing with DI
6.1 Unit Testing
Learning Objectives:
- Mock dependencies for unit tests
- Use dependency injection in tests
- Test service registration
Theory
- Benefits of DI for testing
- Mocking frameworks overview
- Test-driven development with DI
Hands-on Exercise 11: Unit Testing
// Test class
public class OrderServiceTests
{
private readonly Mock<IEmailService> _mockEmailService;
private readonly Mock<IPaymentService> _mockPaymentService;
private readonly OrderService _orderService;
public OrderServiceTests()
{
_mockEmailService = new Mock<IEmailService>();
_mockPaymentService = new Mock<IPaymentService>();
_orderService = new OrderService(_mockEmailService.Object, _mockPaymentService.Object);
}
[Fact]
public async Task ProcessOrder_ShouldCallPaymentService()
{
// Arrange
var order = new Order { Amount = 100, CustomerEmail = "test@example.com" };
// Act
await _orderService.ProcessOrder(order);
// Assert
_mockPaymentService.Verify(x => x.ProcessPayment(100), Times.Once);
_mockEmailService.Verify(x => x.SendConfirmation("test@example.com"), Times.Once);
}
}
Task: Write comprehensive unit tests for the services created in previous exercises.
6.2 Integration Testing
Learning Objectives:
- Test with real DI container
- Use WebApplicationFactory
- Test service lifetimes
Hands-on Exercise 12: Integration Testing
public class ProductControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public ProductControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace services for testing
services.AddScoped<IProductService, TestProductService>();
});
}).CreateClient();
}
[Fact]
public async Task GetProducts_ReturnsSuccessStatusCode()
{
var response = await _client.GetAsync("/api/products");
response.EnsureSuccessStatusCode();
}
}
Task: Create integration tests for the Web API.
Module 7: Performance and Best Practices
7.1 Performance Optimization
Learning Objectives:
- Understand DI performance implications
- Optimize service registration
- Monitor container performance
Theory
- .NET 8 performance improvements
- Container resolution costs
- Best practices for performance
Hands-on Exercise 13: Performance Benchmarking
[MemoryDiagnoser]
public class DIPerformanceBenchmark
{
private ServiceProvider _serviceProvider;
[GlobalSetup]
public void Setup()
{
var services = new ServiceCollection();
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddSingleton<ISingletonService, SingletonService>();
_serviceProvider = services.BuildServiceProvider();
}
[Benchmark]
public ITransientService GetTransientService()
{
return _serviceProvider.GetRequiredService<ITransientService>();
}
[Benchmark]
public ISingletonService GetSingletonService()
{
return _serviceProvider.GetRequiredService<ISingletonService>();
}
}
Task: Benchmark different service lifetimes and registration patterns.
7.2 Best Practices and Common Pitfalls
Learning Objectives:
- Avoid common DI mistakes
- Follow best practices
- Understand captive dependencies
Theory
- Common anti-patterns
- Captive dependency problem
- Service locator anti-pattern
- Best practices checklist
Hands-on Exercise 14: Identifying Problems
// Anti-pattern examples to fix
public class BadService
{
private readonly IServiceProvider _serviceProvider; // Service locator
public BadService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
var service = _serviceProvider.GetService<IAnotherService>(); // Bad!
service.DoSomething();
}
}
// Captive dependency example
public class SingletonService
{
private readonly IScopedService _scopedService; // Problem!
public SingletonService(IScopedService scopedService)
{
_scopedService = scopedService;
}
}
Task: Identify and fix anti-patterns in provided code examples.
Module 8: Advanced Scenarios
8.1 Custom DI Container Integration
Learning Objectives:
- Integrate third-party containers
- Compare container features
- Migration strategies
Theory
- When to use third-party containers
- Popular alternatives (Autofac, Ninject)
- Migration considerations
Hands-on Exercise 15: Autofac Integration
// Program.cs with Autofac
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterType<ProductService>().As<IProductService>().InstancePerLifetimeScope();
containerBuilder.RegisterModule<AutofacModule>();
});
// AutofacModule.cs
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<EmailService>().As<IEmailService>().SingleInstance();
builder.RegisterDecorator<LoggingEmailService, IEmailService>();
}
}
Task: Migrate a portion of the application to use Autofac.
8.2 Background Services and Hosted Services
Learning Objectives:
- Use DI with background services
- Implement hosted services
- Handle scoped services in singletons
Hands-on Exercise 16: Background Processing
public class DataProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<DataProcessingService> _logger;
public DataProcessingService(IServiceProvider serviceProvider, ILogger<DataProcessingService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var dataProcessor = scope.ServiceProvider.GetRequiredService<IDataProcessor>();
await dataProcessor.ProcessDataAsync();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
Task: Implement a background service that processes data using scoped services.
Module 9: Microservices and DI
9.1 DI in Microservices Architecture
Learning Objectives:
- Design DI for microservices
- Handle service discovery
- Implement resilience patterns
Theory
- Microservices DI challenges
- Service discovery patterns
- Circuit breaker and retry patterns
Hands-on Exercise 17: HTTP Client Factory
// Program.cs
builder.Services.AddHttpClient<IOrderService, OrderService>(client =>
{
client.BaseAddress = new Uri("https://api.orders.com");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
});
builder.Services.AddHttpClient<IInventoryService, InventoryService>()
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
// Service implementation
public class OrderService : IOrderService
{
private readonly HttpClient _httpClient;
public OrderService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Order> GetOrderAsync(int orderId)
{
var response = await _httpClient.GetAsync($"/orders/{orderId}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Order>();
}
}
Task: Implement service-to-service communication with resilience patterns.
Module 10: Real-World Project (2 hours)Final Project: E-commerce System
Learning Objectives:
- Apply all learned concepts
- Build a complete system
- Demonstrate best practices
Project Requirements:
- Product catalog service
- Order processing service
- User management service
- Payment integration
- Email notifications
- Background order processing
- Comprehensive testing
- Performance monitoring
Project Structure:
ECommerceSystem/
├── src/
│ ├── ECommerce.API/
│ ├── ECommerce.Core/
│ ├── ECommerce.Infrastructure/
│ └── ECommerce.Services/
├── tests/
│ ├── ECommerce.UnitTests/
│ └── ECommerce.IntegrationTests/
└── docker-compose.yml
Task: Students build a complete e-commerce system applying all DI patterns learned.
Assessment and Certification
Knowledge Check Questions (Throughout Course)
- What are the three main service lifetimes in .NET DI?
- How do you register multiple implementations of the same interface?
- What is the captive dependency problem?
- When should you use keyed services?
- How do you properly test services with dependencies?
Practical Assessments
- Beginner: Convert tightly coupled code to use DI
- Intermediate: Implement a multi-service Web API with proper lifetime management
- Advanced: Design a microservices system with DI best practices
Final Project Evaluation Criteria
- Architecture: Proper separation of concerns
- DI Implementation: Correct service registration and lifetime management
- Testing: Comprehensive unit and integration tests
- Performance: Optimized service resolution
- Best Practices: Following established patterns and avoiding anti-patterns
Additional Resources
Documentation
Tools and Libraries
- Autofac: Advanced DI container
- Scrutor: Assembly scanning for service registration
- BenchmarkDotNet: Performance testing
- Moq: Mocking framework for testing
Upon completing this , you will:
- Understand DI principles and benefits
- Master .NET 8 DI container features
- Implement complex DI scenarios
- Write testable, maintainable code
- Apply DI best practices in real-world projects
- Avoid common pitfalls and anti-patterns