dependency-injection-patterns

Dependency Injection Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dependency-injection-patterns" with this command: npx skills add aaronontheweb/dotnet-skills/aaronontheweb-dotnet-skills-dependency-injection-patterns

Dependency Injection Patterns

When to Use This Skill

Use this skill when:

  • Organizing service registrations in ASP.NET Core applications

  • Avoiding massive Program.cs/Startup.cs files with hundreds of registrations

  • Making service configuration reusable between production and tests

  • Designing libraries that integrate with Microsoft.Extensions.DependencyInjection

Reference Files

  • advanced-patterns.md: Testing with DI extensions, Akka.NET actor scope management, conditional/factory/keyed registration patterns

The Problem

Without organization, Program.cs becomes unmanageable:

// BAD: 200+ lines of unorganized registrations var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IOrderRepository, OrderRepository>(); builder.Services.AddScoped<IProductRepository, ProductRepository>(); builder.Services.AddScoped<IUserService, UserService>(); // ... 150 more lines ...

Problems: hard to find related registrations, no clear boundaries, can't reuse in tests, merge conflicts.

The Solution: Extension Method Composition

Group related registrations into extension methods:

// GOOD: Clean, composable Program.cs var builder = WebApplication.CreateBuilder(args);

builder.Services .AddUserServices() .AddOrderServices() .AddEmailServices() .AddPaymentServices() .AddValidators();

var app = builder.Build();

Extension Method Pattern

Basic Structure

namespace MyApp.Users;

public static class UserServiceCollectionExtensions { public static IServiceCollection AddUserServices(this IServiceCollection services) { services.AddScoped<IUserRepository, UserRepository>(); services.AddScoped<IUserReadStore, UserReadStore>(); services.AddScoped<IUserWriteStore, UserWriteStore>(); services.AddScoped<IUserService, UserService>(); services.AddScoped<IUserValidationService, UserValidationService>();

    return services;
}

}

With Configuration

namespace MyApp.Email;

public static class EmailServiceCollectionExtensions { public static IServiceCollection AddEmailServices( this IServiceCollection services, string configSectionName = "EmailSettings") { services.AddOptions<EmailOptions>() .BindConfiguration(configSectionName) .ValidateDataAnnotations() .ValidateOnStart();

    services.AddSingleton&#x3C;IMjmlTemplateRenderer, MjmlTemplateRenderer>();
    services.AddSingleton&#x3C;IEmailLinkGenerator, EmailLinkGenerator>();
    services.AddScoped&#x3C;IUserEmailComposer, UserEmailComposer>();
    services.AddScoped&#x3C;IEmailSender, SmtpEmailSender>();

    return services;
}

}

File Organization

Place extension methods near the services they register:

src/ MyApp.Api/ Program.cs # Composes all Add* methods MyApp.Users/ Services/ UserService.cs UserServiceCollectionExtensions.cs # AddUserServices() MyApp.Orders/ OrderServiceCollectionExtensions.cs # AddOrderServices() MyApp.Email/ EmailServiceCollectionExtensions.cs # AddEmailServices()

Convention: {Feature}ServiceCollectionExtensions.cs next to the feature's services.

Naming Conventions

Pattern Use For

Add{Feature}Services()

General feature registration

Add{Feature}()

Short form when unambiguous

Configure{Feature}()

When primarily setting options

Use{Feature}()

Middleware (on IApplicationBuilder)

Testing Benefits

The Add* pattern lets you reuse production configuration in tests and only override what's different. Works with WebApplicationFactory, Akka.Hosting.TestKit, and standalone ServiceCollection.

See advanced-patterns.md for complete testing examples.

Layered Extensions

For larger applications, compose extensions hierarchically:

public static class AppServiceCollectionExtensions { public static IServiceCollection AddAppServices(this IServiceCollection services) { return services .AddDomainServices() .AddInfrastructureServices() .AddApiServices(); } }

public static class DomainServiceCollectionExtensions { public static IServiceCollection AddDomainServices(this IServiceCollection services) { return services .AddUserServices() .AddOrderServices() .AddProductServices(); } }

Akka.Hosting Integration

The same pattern works for Akka.NET actor configuration:

public static class OrderActorExtensions { public static AkkaConfigurationBuilder AddOrderActors( this AkkaConfigurationBuilder builder) { return builder .WithActors((system, registry, resolver) => { var orderProps = resolver.Props<OrderActor>(); var orderRef = system.ActorOf(orderProps, "orders"); registry.Register<OrderActor>(orderRef); }); } }

// Usage in Program.cs builder.Services.AddAkka("MySystem", (builder, sp) => { builder .AddOrderActors() .AddInventoryActors() .AddNotificationActors(); });

See akka-hosting-actor-patterns skill for complete Akka.Hosting patterns.

Anti-Patterns

Don't: Register Everything in Program.cs

// BAD: Massive Program.cs with 200+ lines of registrations

Don't: Create Overly Generic Extensions

// BAD: Too vague, doesn't communicate what's registered public static IServiceCollection AddServices(this IServiceCollection services) { ... }

Don't: Hide Important Configuration

// BAD: Buried settings public static IServiceCollection AddDatabase(this IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer("hardcoded-connection-string")); // Hidden! }

// GOOD: Accept configuration explicitly public static IServiceCollection AddDatabase( this IServiceCollection services, string connectionString) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString)); }

Best Practices Summary

Practice Benefit

Group related services into Add* methods Clean Program.cs, clear boundaries

Place extensions near the services they register Easy to find and maintain

Return IServiceCollection for chaining Fluent API

Accept configuration parameters Flexibility

Use consistent naming (Add{Feature}Services ) Discoverability

Test by reusing production extensions Confidence, less duplication

Lifetime Management

Lifetime Use When Examples

Singleton Stateless, thread-safe, expensive to create Configuration, HttpClient factories, caches

Scoped Stateful per-request, database contexts DbContext, repositories, user context

Transient Lightweight, stateful, cheap to create Validators, short-lived helpers

// SINGLETON: Stateless services, shared safely services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();

// SCOPED: Database access, per-request state services.AddScoped<IUserRepository, UserRepository>();

// TRANSIENT: Cheap, short-lived services.AddTransient<CreateUserRequestValidator>();

Scoped services require a scope. ASP.NET Core creates one per HTTP request. In background services and actors, create scopes manually.

See advanced-patterns.md for actor scope management patterns.

Common Mistakes

Injecting Scoped into Singleton

// BAD: Singleton captures scoped service - stale DbContext! public class CacheService // Registered as Singleton { private readonly IUserRepository _repo; // Scoped - captured at startup! }

// GOOD: Inject IServiceProvider, create scope per operation public class CacheService { private readonly IServiceProvider _serviceProvider;

public async Task&#x3C;User> GetUserAsync(string id)
{
    using var scope = _serviceProvider.CreateScope();
    var repo = scope.ServiceProvider.GetRequiredService&#x3C;IUserRepository>();
    return await repo.GetByIdAsync(id);
}

}

No Scope in Background Work

// BAD: No scope for scoped services public class BadBackgroundService : BackgroundService { private readonly IOrderService _orderService; // Scoped - will throw! }

// GOOD: Create scope for each unit of work public class GoodBackgroundService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory;

protected override async Task ExecuteAsync(CancellationToken ct)
{
    using var scope = _scopeFactory.CreateScope();
    var orderService = scope.ServiceProvider.GetRequiredService&#x3C;IOrderService>();
    // ...
}

}

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

modern-csharp-coding-standards

No summary provided by upstream source.

Repository SourceNeeds Review
General

efcore-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

csharp-concurrency-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

playwright-blazor-testing

No summary provided by upstream source.

Repository SourceNeeds Review