# .NET Dependency Injection Standards

> **Scope:** universal
> **Layer:** 0 (always load)
> **Keywords:** di, dependency injection, ioc, lifetime, scoped, singleton, transient
> **Load When:** always

Dependency Injection patterns and lifetimes for .NET applications

---

## Service Lifetimes

### Transient

**Created every time they're requested**

Use for: Lightweight, stateless services

```csharp
// Program.cs
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddTransient<IPdfGenerator, PdfGenerator>();
```

### Scoped

**Created once per request (HTTP request in web apps)**

Use for: DbContext, repositories, request-specific services

```csharp
// Program.cs
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddDbContext<AppDbContext>(); // Scoped by default
```

### Singleton

**Created once for application lifetime**

Use for: Caching, configuration, thread-safe services

```csharp
// Program.cs
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
```

---

## Registration Patterns

### Interface + Implementation

```csharp
// ✅ CORRECT
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

// ❌ WRONG (concrete types in constructor)
public class OrderController
{
    private readonly OrderRepository _repo; // Don't inject concrete types!

    public OrderController(OrderRepository repo) { }
}
```

### Multiple Implementations

```csharp
// Register multiple implementations
builder.Services.AddScoped<INotificationService, EmailNotificationService>();
builder.Services.AddScoped<INotificationService, SmsNotificationService>();

// Inject IEnumerable<T>
public class NotificationManager
{
    private readonly IEnumerable<INotificationService> _services;

    public NotificationManager(IEnumerable<INotificationService> services)
    {
        _services = services;
    }

    public async Task NotifyAllAsync(string message)
    {
        foreach (var service in _services)
        {
            await service.SendAsync(message);
        }
    }
}
```

### Factory Pattern

```csharp
// For services that need per-operation instances
builder.Services.AddScoped<IUserRepositoryFactory, UserRepositoryFactory>();

public interface IUserRepositoryFactory
{
    IScopedUserRepository CreateScoped();
}

public class UserRepositoryFactory : IUserRepositoryFactory
{
    private readonly IDbContextFactory<AppDbContext> _contextFactory;

    public UserRepositoryFactory(IDbContextFactory<AppDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public IScopedUserRepository CreateScoped()
    {
        var context = _contextFactory.CreateDbContext();
        return new ScopedUserRepository(context);
    }
}
```

---

## Constructor Injection

### Primary Constructors (.NET 8+)

```csharp
// ✅ CORRECT (modern syntax)
public class UserService(
    IUserRepository repository,
    ILogger<UserService> logger,
    IEmailService emailService) : IUserService
{
    public async Task<User> GetUserAsync(int id)
    {
        logger.LogInformation("Getting user {UserId}", id);
        return await repository.GetByIdAsync(id);
    }
}
```

### Traditional Constructors

```csharp
// ✅ CORRECT
public class UserService : IUserService
{
    private readonly IUserRepository _repository;
    private readonly ILogger<UserService> _logger;

    public UserService(IUserRepository repository, ILogger<UserService> logger)
    {
        _repository = repository;
        _logger = logger;
    }
}
```

---

## Blazor-Specific DI

### DbContext in Blazor Components

**❌ WRONG: Direct DbContext Injection**

```csharp
// ❌ WRONG (DbContext lifecycle issue)
@inject AppDbContext DbContext

@code {
    protected override async Task OnInitializedAsync()
    {
        users = await DbContext.Users.ToListAsync(); // May be disposed!
    }
}
```

**✅ CORRECT: Use IDbContextFactory**

```csharp
// Program.cs
builder.Services.AddDbContextFactory<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

// Component
@inject IDbContextFactory<AppDbContext> DbFactory

@code {
    protected override async Task OnInitializedAsync()
    {
        await using var context = await DbFactory.CreateDbContextAsync();
        users = await context.Users.ToListAsync();
    }
}
```

### Service Injection in Components

```csharp
@inject IUserService UserService
@inject NavigationManager Navigation
@inject ILogger<UserList> Logger

@code {
    private List<User> users = new();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            users = await UserService.GetAllAsync();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Failed to load users");
        }
    }
}
```

---

## Lifetime Conflicts

### Problem: Scoped in Singleton

```csharp
// ❌ WRONG (Scoped DbContext injected in Singleton)
builder.Services.AddSingleton<CacheService>(); // Singleton
builder.Services.AddDbContext<AppDbContext>(); // Scoped

public class CacheService
{
    private readonly AppDbContext _context; // ❌ WRONG!

    public CacheService(AppDbContext context)
    {
        _context = context; // Scoped injected in Singleton!
    }
}
```

**✅ CORRECT: Use IServiceProvider or Factory**

```csharp
public class CacheService
{
    private readonly IDbContextFactory<AppDbContext> _factory;

    public CacheService(IDbContextFactory<AppDbContext> factory)
    {
        _factory = factory;
    }

    public async Task<User> GetUserAsync(int id)
    {
        await using var context = await _factory.CreateDbContextAsync();
        return await context.Users.FindAsync(id);
    }
}
```

---

## Registration Order

### Program.cs Order

```csharp
var builder = WebApplication.CreateBuilder(args);

// 1. Configuration
var config = builder.Configuration;

// 2. Logging
builder.Services.AddLogging();

// 3. Database
builder.Services.AddDbContextFactory<AppDbContext>(options =>
    options.UseSqlServer(config.GetConnectionString("DefaultConnection")));

// 4. Repositories (depend on DbContext)
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

// 5. Services (depend on repositories)
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderService, OrderService>();

// 6. Infrastructure
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();

// 7. External integrations
builder.Services.AddHttpClient<IPaymentGateway, StripePaymentGateway>();

// 8. Framework services (last)
builder.Services.AddControllers();
builder.Services.AddRazorComponents();

var app = builder.Build();
```

---

## Best Practices

### DO

✅ **Inject interfaces, not concrete types**

```csharp
// ✅ CORRECT
public class OrderService(IOrderRepository repository) { }

// ❌ WRONG
public class OrderService(OrderRepository repository) { }
```

✅ **Use primary constructors (.NET 8+)**

```csharp
// ✅ CORRECT (concise)
public class UserService(IUserRepository repository, ILogger<UserService> logger) : IUserService
{
    public async Task<User> GetAsync(int id) =>
        await repository.GetByIdAsync(id);
}
```

✅ **Use factories for DbContext in singletons**

```csharp
// ✅ CORRECT
public class CacheService(IDbContextFactory<AppDbContext> factory)
{
    public async Task LoadAsync()
    {
        await using var context = await factory.CreateDbContextAsync();
        // Use context
    }
}
```

### DON'T

❌ **Don't inject scoped services in singletons**

```csharp
// ❌ WRONG
builder.Services.AddSingleton<CacheService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();

public class CacheService(IUserRepository repository) // ❌ Scoped in Singleton!
{
}
```

❌ **Don't use service locator pattern**

```csharp
// ❌ WRONG (anti-pattern)
public class OrderService
{
    public async Task ProcessAsync(int orderId)
    {
        var serviceProvider = ...; // Don't do this!
        var repo = serviceProvider.GetService<IOrderRepository>();
    }
}

// ✅ CORRECT (constructor injection)
public class OrderService(IOrderRepository repository)
{
    public async Task ProcessAsync(int orderId)
    {
        var order = await repository.GetByIdAsync(orderId);
    }
}
```

❌ **Don't create instances with `new`**

```csharp
// ❌ WRONG
public class OrderService
{
    public async Task ProcessAsync()
    {
        var emailService = new EmailService(); // Don't create manually!
        await emailService.SendAsync(...);
    }
}

// ✅ CORRECT
public class OrderService(IEmailService emailService)
{
    public async Task ProcessAsync()
    {
        await emailService.SendAsync(...);
    }
}
```

---

## Testing with DI

### Mock Dependencies

```csharp
[Fact]
public async Task GetUser_ValidId_ReturnsUser()
{
    // Arrange
    var mockRepo = new Mock<IUserRepository>();
    mockRepo.Setup(r => r.GetByIdAsync(1))
            .ReturnsAsync(new User { Id = 1, Name = "John" });

    var service = new UserService(mockRepo.Object, Mock.Of<ILogger<UserService>>());

    // Act
    var user = await service.GetUserAsync(1);

    // Assert
    Assert.NotNull(user);
    Assert.Equal("John", user.Name);
}
```

---

*MORPH-SPEC by Polymorphism Tech*
