本文へ移動
cccskills
無料GitHub で公開

dotnet-csharp-dependency-injection

Registers and resolves services with MS DI. Keyed services, scopes, decoration, lifetimes.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md11.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

dotnet-csharp-dependency-injection

Advanced Microsoft.Extensions.DependencyInjection patterns for .NET applications. Covers service lifetimes, keyed services (net8.0+), decoration, factory delegates, scope validation, and hosted service registration.

Scope

  • Service lifetimes (transient, scoped, singleton) and captive dependency detection
  • Keyed services (.NET 8+) and factory delegates
  • Decorator pattern and scope validation
  • Hosted service registration

Out of scope

  • Async/await patterns in BackgroundService -- see [skill:dotnet-csharp-async-patterns]
  • Options pattern binding and IOptions<T> -- see [skill:dotnet-csharp-configuration]
  • SOLID/DRY design principles -- see [skill:dotnet-solid-principles]

Cross-references: [skill:dotnet-csharp-async-patterns] for BackgroundService async patterns, [skill:dotnet-csharp-configuration] for IOptions<T> binding.


Service Lifetimes

LifetimeRegistrationWhen to Use
TransientAddTransient<T>()Lightweight, stateless services. New instance per injection.
ScopedAddScoped<T>()Per-request state (EF Core DbContext, unit of work).
SingletonAddSingleton<T>()Thread-safe, stateless, or shared state (caches, config).

Lifetime Mismatches (Captive Dependencies)

Never inject a shorter-lived service into a longer-lived one:


// WRONG -- scoped DbContext captured in singleton = same context for all requests
builder.Services.AddSingleton<OrderService>();    // singleton
builder.Services.AddScoped<AppDbContext>();        // scoped -- CAPTIVE!

// CORRECT -- use IServiceScopeFactory in singletons
public sealed class OrderService(IServiceScopeFactory scopeFactory)
{
    public async Task ProcessAsync(CancellationToken ct = default)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Orders.Where(o => o.IsPending).ToListAsync(ct);
    }
}

```text

### Enable Scope Validation (Development)

```csharp

var builder = WebApplication.CreateBuilder(args);
// In Development, ValidateScopes is already true by default.
// For non-web hosts:
var host = Host.CreateDefaultBuilder(args)
    .UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = true;
        options.ValidateOnBuild = true;  // Validates all registrations at startup
    })
    .Build();

```text

---

## Registration Patterns

### Interface-Implementation Pair

```csharp

builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();

```csharp

### Multiple Implementations

```csharp

// Register multiple implementations
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
builder.Services.AddScoped<INotifier, PushNotifier>();

// Inject all -- order matches registration order
public sealed class CompositeNotifier(IEnumerable<INotifier> notifiers)
{
    public async Task NotifyAsync(string message, CancellationToken ct = default)
    {
        foreach (var notifier in notifiers)
        {
            await notifier.NotifyAsync(message, ct);
        }
    }
}

```text

### Factory Delegates

```csharp

builder.Services.AddScoped<IOrderService>(sp =>
{
    var repo = sp.GetRequiredService<IOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<OrderService>>();
    var options = sp.GetRequiredService<IOptions<OrderOptions>>();
    return new OrderService(repo, logger, options.Value.MaxRetries);
});

```text

### `TryAdd` for Library Registrations

Libraries should use `TryAdd` so applications can override:

```csharp

// Library code -- won't overwrite app registrations
builder.Services.TryAddScoped<IOrderRepository, DefaultOrderRepository>();

// Application code -- takes precedence if registered first
builder.Services.AddScoped<IOrderRepository, CustomOrderRepository>();

```text

---

## Keyed Services (net8.0+)

Register and resolve services by a key, replacing the need for named service patterns.

```csharp

// Registration
builder.Services.AddKeyedScoped<ICache, RedisCache>("distributed");
builder.Services.AddKeyedScoped<ICache, MemoryCache>("local");

// Injection via attribute
public sealed class OrderService(
    [FromKeyedServices("distributed")] ICache distributedCache,
    [FromKeyedServices("local")] ICache localCache)
{
    public async Task<Order?> GetAsync(int id, CancellationToken ct = default)
    {
        // Check local cache first, then distributed
        return await localCache.GetAsync<Order>(id.ToString(), ct)
            ?? await distributedCache.GetAsync<Order>(id.ToString(), ct);
    }
}

// Manual resolution
var cache = sp.GetRequiredKeyedService<ICache>("distributed");

```text

> **net8.0+ only.** On earlier TFMs, use factory patterns or a dictionary-based approach.

---

## Decoration Pattern

The built-in container does not natively support decoration. Use one of these approaches:

### Manual Decoration

```csharp

builder.Services.AddScoped<SqlOrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
{
    var inner = sp.GetRequiredService<SqlOrderRepository>();
    var logger = sp.GetRequiredService<ILogger<LoggingOrderRepository>>();
    return new LoggingOrderRepository(inner, logger);
});

public sealed class LoggingOrderRepository(
    IOrderRepository inner,
    ILogger<LoggingOrderRepository> logger) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        logger.LogInformation("Getting order {OrderId}", id);
        return await inner.GetByIdAsync(id, ct);
    }
}

```text

### Scrutor Library (Popular Alternative)

```csharp

builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.Decorate<IOrderRepository, LoggingOrderRepository>();
builder.Services.Decorate<IOrderRepository, CachingOrderRepository>();
// Outer -> CachingOrderRepository -> LoggingOrderRepository -> SqlOrderRepository

```text

---

## Hosted Services and Background Workers

### `BackgroundService` (Preferred)

```csharp

public sealed class QueueProcessorWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<QueueProcessorWorker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Queue processor starting");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = scopeFactory.CreateScope();
                var processor = scope.ServiceProvider
                    .GetRequiredService<IQueueProcessor>();

                await processor.ProcessNextBatchAsync(stoppingToken);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                logger.LogError(ex, "Error processing queue batch");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }
}

// Registration
builder.Services.AddHostedService<QueueProcessorWorker>();

```text

### `IHostedService` (Startup/Shutdown Hooks)

```csharp

public sealed class DatabaseMigrationService(
    IServiceScopeFactory scopeFactory,
    ILogger<DatabaseMigrationService> logger) : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync(cancellationToken);
        logger.LogInformation("Database migration completed");
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

builder.Services.AddHostedService<DatabaseMigrationService>();

```text

### Key Rules for Hosted Services

- Always use `IServiceScopeFactory` to create scopes -- hosted services are singletons
- Never inject scoped services directly into hosted service constructors
- Handle exceptions inside `ExecuteAsync` -- unhandled exceptions stop the host (net8.0+)
- See [skill:dotnet-csharp-async-patterns] for async patterns in background workers

---

## Organizing Registrations

Group related registrations into extension methods for clean `Program.cs`:

```csharp

// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddOrderServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderRepository, SqlOrderRepository>();
        services.AddScoped<IOrderService, OrderService>();
        services.AddHostedService<OrderProcessorWorker>();
        return services;
    }

    public static IServiceCollection AddNotificationServices(this IServiceCollection services)
    {
        services.AddScoped<INotifier, EmailNotifier>();
        services.AddScoped<INotifier, SmsNotifier>();
        return services;
    }
}

// Program.cs
builder.Services.AddOrderServices();
builder.Services.AddNotificationServices();

```csharp

---

## Testing with DI

```csharp

[Fact]
public async Task OrderService_UsesRepository()
{
    // Arrange -- build a service provider for integration tests
    var services = new ServiceCollection();
    services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
    services.AddScoped<IOrderService, OrderService>();
    services.AddLogging();

    using var provider = services.BuildServiceProvider();
    using var scope = provider.CreateScope();
    var service = scope.ServiceProvider.GetRequiredService<IOrderService>();

    // Act
    var order = await service.GetByIdAsync(1);

    // Assert
    Assert.NotNull(order);
}

```text

For unit tests, prefer direct constructor injection with mocks rather than building a full container.

---



## Code Navigation (Serena MCP)

**Primary approach:** Use Serena symbol operations for efficient code navigation:

1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications

**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine

**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"

# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References

- [Dependency injection in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection)
- [Keyed services in .NET 8](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services)
- [Background tasks with hosted services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services)
- [Service lifetimes](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes)
- [.NET Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Patterns and techniques for evaluating and improving AI agent outputs.

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

deep-wiki

無料

AI-powered wiki generation for code repositories with commands, agents, and skills

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.

日本語の概要は準備中です。原文の説明を表示しています。

rudironsoni/Synaxis22026年3月17日 更新

rudironsoni のスキルをすべて見る

このスキルの問題を報告する