Skip to main content Home Creators aaronontheweb dotnet-skills dependency-injection-patterns
dependency-injection-patterns Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/Aaronontheweb/dotnet-skills --skill dependency-injection-patternsThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository
csharp-nullable-reference-types Guidelines for introducing and using nullable reference types (NRT) and System.Diagnostics.CodeAnalysis nullable attributes in C# / .NET codebases. Covers the nullability model, flow analysis, the null-forgiving operator, API design rules, the full attribute catalog (AllowNull, DisallowNull, MaybeNull, NotNull, NotNullWhen, MaybeNullWhen, NotNullIfNotNull, MemberNotNull, MemberNotNullWhen, DoesNotReturn, DoesNotReturnIf), the C# 14 field keyword, incremental migration of legacy codebases, and a code-generation checklist.
opentelemetry-net-instrumentation Provides guidance for implementing OpenTelemetry instrumentation in .NET codebases, covering tracing (Activities/Spans), metrics, logs, naming conventions, error handling, performance, SDK setup, resources, context propagation, and API design best practices.
Related occupations SOC
Based on SOC occupation classification
advanced-patterns.md 7.2 KB name dependency-injection-patterns description Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests. invocable false
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:
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>();
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:
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<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddSingleton<IEmailLinkGenerator, EmailLinkGenerator>();
services.AddScoped<IUserEmailComposer, UserEmailComposer>();
services.AddScoped<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.
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);
});
}
}
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
Don't: Create Overly Generic Extensions
public static IServiceCollection AddServices (this IServiceCollection services ) { ... }
Don't: Hide Important Configuration
public static IServiceCollection AddDatabase (this IServiceCollection services )
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer("hardcoded-connection-string" ));
}
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
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddTransient<CreateUserRequestValidator>();
Scoped services require a scope. ASP.NET Core creates one per HTTP request. In background services and actors, create scopes manually.
Common Mistakes
Injecting Scoped into Singleton
public class CacheService
{
private readonly IUserRepository _repo;
}
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public async Task<User> GetUserAsync (string id )
{
using var scope = _serviceProvider.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
return await repo.GetByIdAsync(id);
}
}
No Scope in Background Work
public class BadBackgroundService : BackgroundService
{
private readonly IOrderService _orderService;
}
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<IOrderService>();
}
}
Resources