dependency-injection-patterns — скилл для Claude Code: DI
Скилл учит раскладывать регистрации сервисов в ASP.NET Core по расширениям IServiceCollection, чтобы Program.cs не распухал.
npx skills add Aaronontheweb/dotnet-skills --skill dependency-injection-patterns -a claude-code
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a claude-codeЧто делает dependency-injection-patterns
Скилл показывает, как группировать связанные регистрации в методы-расширения вида AddUserServices() или AddEmailServices(). Внутри — примеры структуры расширений, работа с конфигурацией через AddOptions и BindConfiguration, а также схема раскладки файлов: {Feature}ServiceCollectionExtensions.cs лежит рядом с сервисами своей фичи.
Для крупных приложений есть слоёные расширения: AddAppServices() собирает доменные, инфраструктурные и API-регистрации в одну цепочку. Отдельный раздел посвящён Akka.Hosting — тот же приём применяется к конфигурации акторов через AkkaConfigurationBuilder. В справочном файле advanced-patterns.md разобраны тестирование с DI-расширениями, условная и фабричная регистрация, keyed-регистрации.
Скилл не требует ничего доустанавливать, кроме самого репозитория. Учтите: он описывает паттерн, а не проверяет ваш код — если в проекте уже есть свои конвенции именования расширений, скилл будет советовать Add{Feature}Services(), и это стоит сверить с командой до того, как переписывать Program.cs.
Какие задачи решает
- разложить сотни регистраций из
Program.csпо фичам - переиспользовать продакшн-конфигурацию сервисов в тестах
- подключить настройки через
AddOptionsс валидацией - собрать слоёные расширения для крупного приложения
- перенести тот же приём на конфигурацию акторов Akka.Hosting
Примеры запросов агенту
- Разбей регистрации в Program.cs на Add*-расширения по фичам
- Сделай AddEmailServices с привязкой к секции конфигурации EmailSettings
- Покажи, как переиспользовать эти расширения в тестах через WebApplicationFactory
Как установить dependency-injection-patterns
1Через Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill dependency-injection-patterns -a claude-codenpx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a claude-code2Вручную: положить файлы
~/.claude/skills/microsoft-extensions-dependency-injection/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/microsoft-extensions-dependency-injection/
1Через Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a codex2Вручную: положить файлы
.agents/skills/microsoft-extensions-dependency-injection/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a cursor2Вручную: положить файлы
.cursor/skills/microsoft-extensions-dependency-injection/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install Aaronontheweb/dotnet-skills microsoft-extensions-dependency-injection2Вручную: положить файлы
.github/skills/microsoft-extensions-dependency-injection/SKILL.md1Через Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a gemini-cli2Командой платформы
gemini skills install https://github.com/Aaronontheweb/dotnet-skills --path skills/microsoft-extensions-dependency-injectionВручную: положить файлы
.devin/skills/microsoft-extensions-dependency-injection/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о dependency-injection-patterns
Как установить dependency-injection-patterns в Claude?
Выполните npx skills add Aaronontheweb/dotnet-skills --skill dependency-injection-patterns -a claude-code, затем при необходимости добавьте второй скилл: npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-dependency-injection -a claude-code. Файл скилла окажется по пути ~/.claude/skills/microsoft-extensions-dependency-injection/SKILL.md.
Что делает dependency-injection-patterns?
Описывает, как группировать регистрации сервисов в методы-расширения IServiceCollection и держать Program.cs читаемым. Заодно показывает, как те же расширения переиспользовать в тестах.
Скилл работает только с ASP.NET Core?
Основные примеры — про ASP.NET Core и Microsoft.Extensions.DependencyInjection, но тот же паттерн показан для Akka.Hosting: расширения над AkkaConfigurationBuilder для регистрации акторов.
Исходный текст
SKILL.md · MIT
---
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.
user-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](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:
```csharp
// 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:
```csharp
// 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
```csharp
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
```csharp
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.
See [advanced-patterns.md](advanced-patterns.md) for complete testing examples.
---
## Layered Extensions
For larger applications, compose extensions hierarchically:
```csharp
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:
```csharp
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
```csharp
// BAD: Massive Program.cs with 200+ lines of registrations
```
### Don't: Create Overly Generic Extensions
```csharp
// BAD: Too vague, doesn't communicate what's registered
public static IServiceCollection AddServices(this IServiceCollection services) { ... }
```
### Don't: Hide Important Configuration
```csharp
// 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 |
```csharp
// 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](advanced-patterns.md) for actor scope management patterns.
---
## Common Mistakes
### Injecting Scoped into Singleton
```csharp
// 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<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
```csharp
// 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<IOrderService>();
// ...
}
}
```
---
## Resources
- **Microsoft.Extensions.DependencyInjection**: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
- **Akka.Hosting**: https://github.com/akkadotnet/Akka.Hosting
- **Akka.DependencyInjection**: https://getakka.net/articles/actors/dependency-injection.html
- **Options Pattern**: See `microsoft-extensions-configuration` skill
© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills