.claude/skills/dotnet-architecture/SKILL.md
Guides implementation of Clean Architecture in .NET projects. Covers all layers: DTO, Domain (Models, Services, Enums), Infra.Interfaces (Repository, AppServices), Infra (Context, Repository, AppServices), and Application (DI/Startup). Use when creating entities, services, repositories, or restructuring architecture. Works with any project type (Web, Console, Mobile, Windows).
npx skillsauth add emaginebr/Peleja dotnet-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert assistant that helps developers implement Clean Architecture in .NET projects. You guide the user through ALL required layers. This guide is agnostic to the presentation layer — it works for Web API, Console, Mobile (MAUI), Windows (WPF/WinForms), Worker Services, etc.
The user will describe what to create or modify: $ARGUMENTS
Before generating code:
Startup.cs)┌─────────────────────────────────────────────────────────────┐
│ Presentation (Console, API, Mobile, Windows, Worker, etc.) │
│ Depends on: Application │
└──────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────┐
│ Application — DI/IoC, Startup, cross-cutting concerns │
│ Depends on: everything needed to wire up the application │
└──────────────────────────────┬──────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Domain │ │ Infra │ │ DTO │
│ Models, Services │ │ Context, Repos, │ │ Data contracts, │
│ Enums, Rules │ │ AppServices │ │ IOptions configs │
│ │ │ │ │ │
│ Depends on: │ │ Depends on: │ │ Depends on: │
│ Infra.Interfaces │ │ Domain, │ │ nothing │
│ DTO │ │ Infra.Interfaces │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Infra.Interfaces — Repository & AppService contracts │
│ Depends on: nothing (may depend on DTO only) │
│ Uses generics to avoid coupling to Domain models │
└─────────────────────────────────────────────────────────────┘
| Layer | Project | Responsibility | Dependencies |
|-------|---------|---------------|-------------|
| DTO | {Project}.DTO | Data Transfer Objects, public contracts, IOptions<T> configuration classes | None |
| Infra.Interfaces | {Project}.Infra.Interfaces | Repository and AppService interfaces using generics (<TModel>) | None (may depend on DTO) |
| Domain | {Project}.Domain | Models (rich entities), Enums, Services (business rules), Service interfaces | Infra.Interfaces, DTO |
| Infra | {Project}.Infra | DbContext, Repository implementations, AppService implementations (SNS, Rabbit, GitHub, Email, etc.) | Domain, Infra.Interfaces |
| Application | {Project}.Application | Centralized DI/IoC via Startup.cs, cross-cutting concerns | All layers as needed |
| Presentation | Varies | UI/entry point — NOT covered by this skill | Application |
Startup classDomain/Interfaces/I{Entity}Model when there's a specific needCreate data transfer objects in the {Project}.DTO project root.
namespace {Project}.DTO;
public class {Entity}Info
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
}
Guidelines:
{Entity}InsertInfo (no Id), {Entity}UpdateInfo (with Id)DateTime?, long?)IOptions<T> also live here (e.g., DatabaseSettings, OpenAISettings)Create repository interfaces in {Project}.Infra.Interfaces/Repository/ using generics.
namespace {Project}.Infra.Interfaces.Repository;
public interface I{Entity}Repository<TModel> where TModel : class
{
Task<TModel> GetByIdAsync(long id);
Task<List<TModel>> ListAllAsync();
Task<TModel> InsertAsync(TModel entity);
Task<TModel> UpdateAsync(TModel entity);
Task DeleteAsync(long id);
}
Guidelines:
<TModel>) — the interface must NOT reference Domain models directlywhere TModel : class is sufficientTask<(List<TModel> Items, int TotalCount)>Create AppService interfaces in {Project}.Infra.Interfaces/AppServices/ for infrastructure services.
namespace {Project}.Infra.Interfaces.AppServices;
public interface I{External}AppService<TModel> where TModel : class
{
Task<TModel> GetDataAsync(string identifier);
Task SendAsync(TModel data);
}
Guidelines:
IEmailAppService), it's acceptable to use primitive types or DTO types insteadCreate rich domain entities in {Project}.Domain/Models/.
namespace {Project}.Domain.Models;
public class {Entity}
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Business behavior methods
public void UpdateName(string newName)
{
if (string.IsNullOrWhiteSpace(newName))
throw new ArgumentException("Name cannot be empty.", nameof(newName));
Name = newName;
}
}
Guidelines:
I{Entity}) are exceptional — only create when there's a specific need (e.g., multiple implementations, testing boundaries){Project}.Domain/Enums/Vector, etc.) — use primitive types (float[], string, byte[]) and let Infra handle conversionCreate service interfaces in {Project}.Domain/Interfaces/.
namespace {Project}.Domain.Interfaces;
public interface I{Entity}Service
{
Task<{Entity}Info> GetByIdAsync(long id);
Task<List<{Entity}Info>> ListAllAsync();
Task<{Entity}Info> CreateAsync({Entity}Info dto);
Task<{Entity}Info> UpdateAsync({Entity}Info dto);
Task DeleteAsync(long id);
}
Guidelines:
Create service implementations in {Project}.Domain/Services/.
using {Project}.Domain.Interfaces;
using {Project}.Domain.Models;
using {Project}.DTO;
using {Project}.Infra.Interfaces.Repository;
namespace {Project}.Domain.Services;
public class {Entity}Service : I{Entity}Service
{
private readonly I{Entity}Repository<{Entity}> _repository;
public {Entity}Service(I{Entity}Repository<{Entity}> repository)
{
_repository = repository;
}
public async Task<{Entity}Info> GetByIdAsync(long id)
{
var entity = await _repository.GetByIdAsync(id);
return MapToDto(entity);
}
public async Task<{Entity}Info> CreateAsync({Entity}Info dto)
{
var entity = MapToModel(dto);
entity.CreatedAt = DateTime.UtcNow;
var saved = await _repository.InsertAsync(entity);
return MapToDto(saved);
}
// Manual mapping — or use AutoMapper/Mapster if project uses it
private static {Entity}Info MapToDto({Entity} entity) => new()
{
Id = entity.Id,
Name = entity.Name,
CreatedAt = entity.CreatedAt
};
private static {Entity} MapToModel({Entity}Info dto) => new()
{
Id = dto.Id,
Name = dto.Name
};
}
Guidelines:
Add DbSet and configure the entity in OnModelCreating.
// Add DbSet
public DbSet<{Entity}> {Entity}s { get; set; }
// Inside OnModelCreating:
modelBuilder.Entity<{Entity}>(entity =>
{
entity.ToTable("{table_name}");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.HasColumnName("id")
.UseIdentityAlwaysColumn();
entity.Property(e => e.Name)
.HasColumnName("name")
.HasMaxLength(240)
.IsRequired();
entity.Property(e => e.CreatedAt)
.HasColumnName("created_at")
.HasColumnType("timestamp with time zone")
.HasDefaultValueSql("now()");
});
Conventions to detect and match:
snake_case vs PascalCase vs plural vs singularsnake_case (created_at) vs PascalCase (CreatedAt)timestamp with time zone (PostgreSQL) vs datetime2 (SQL Server)DeleteBehavior.Cascade vs ClientSetNull vs RestrictValueConverter when Domain model types differ from database types (e.g., float[] → pgvector Vector)using {Project}.Domain.Models;
using {Project}.Infra.Context;
using {Project}.Infra.Interfaces.Repository;
using Microsoft.EntityFrameworkCore;
namespace {Project}.Infra.Repository;
public class {Entity}Repository : I{Entity}Repository<{Entity}>
{
private readonly {DbContextType} _context;
public {Entity}Repository({DbContextType} context)
{
_context = context;
}
public async Task<{Entity}> GetByIdAsync(long id)
{
return await _context.{Entity}s
.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == id)
?? throw new KeyNotFoundException($"{Entity} {id} not found.");
}
public async Task<List<{Entity}>> ListAllAsync()
{
return await _context.{Entity}s
.AsNoTracking()
.OrderBy(e => e.Name)
.ToListAsync();
}
public async Task<{Entity}> InsertAsync({Entity} entity)
{
_context.{Entity}s.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
public async Task<{Entity}> UpdateAsync({Entity} entity)
{
var existing = await _context.{Entity}s.FindAsync(entity.Id)
?? throw new KeyNotFoundException($"{Entity} {entity.Id} not found.");
_context.Entry(existing).CurrentValues.SetValues(entity);
await _context.SaveChangesAsync();
return existing;
}
public async Task DeleteAsync(long id)
{
var entity = await _context.{Entity}s.FindAsync(id)
?? throw new KeyNotFoundException($"{Entity} {id} not found.");
_context.{Entity}s.Remove(entity);
await _context.SaveChangesAsync();
}
}
using {Project}.Domain.Models;
using {Project}.Infra.Interfaces.AppServices;
namespace {Project}.Infra.AppServices;
public class {External}AppService : I{External}AppService<{Model}>
{
private readonly HttpClient _httpClient;
public {External}AppService(HttpClient httpClient)
{
_httpClient = httpClient;
}
// Implementation that calls external service
}
Examples of AppServices:
GitHubAppService — calls GitHub API via OctokitOpenAIAppService — calls OpenAI API for completions/embeddingsSnsAppService — publishes messages to AWS SNSRabbitAppService — publishes/consumes RabbitMQ messagesEmailAppService — sends emails via SMTP/SendGridStorageAppService — uploads/downloads from S3/Azure Blobdotnet ef migrations add Add{Entity}Table --project {InfraProject} --startup-project {StartupProject}
dotnet ef database update --project {InfraProject} --startup-project {StartupProject}
All DI registration is centralized in the Startup class.
using {Project}.Domain.Interfaces;
using {Project}.Domain.Models;
using {Project}.Domain.Services;
using {Project}.Infra.Context;
using {Project}.Infra.Interfaces.Repository;
using {Project}.Infra.Repository;
using {Project}.Infra.AppServices;
using {Project}.Infra.Interfaces.AppServices;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace {Project}.Application;
public static class Startup
{
public static IServiceCollection ConfigureServices(
this IServiceCollection services,
Action<{Settings}> configureSettings)
{
var settings = new {Settings}();
configureSettings(settings);
// IOptions configuration
services.Configure<{SubSettings}>(opt => { /* ... */ });
// DbContext
services.AddDbContext<{DbContext}>(options =>
options.UseNpgsql(settings.Database.ConnectionString));
// Repositories
services.AddScoped<I{Entity}Repository<{Entity}>, {Entity}Repository>();
// AppServices
services.AddHttpClient<I{External}AppService<{Model}>, {External}AppService>();
// Domain Services
services.AddScoped<I{Entity}Service, {Entity}Service>();
return services;
}
}
Guidelines:
Startup class — all registrations in one placeAddScoped (per-request), AddTransient (per-resolve), AddSingleton (app lifetime)AddHttpClient<> for services that need HttpClient (OpenAI, GitHub, etc.)services.AddAutoMapper(typeof(Startup).Assembly) if used| # | Layer | Action | Description |
|---|-------|--------|-------------|
| 1 | DTO | Create | {Entity}Info.cs (and Insert/Update variants if needed) |
| 2 | Infra.Interfaces | Create | I{Entity}Repository<TModel> in Repository/ |
| 3 | Infra.Interfaces | Create | I{External}AppService<TModel> in AppServices/ (if applicable) |
| 4 | Domain | Create | {Entity}.cs model in Models/ |
| 5 | Domain | Create | I{Entity}Service.cs interface in Interfaces/ |
| 6 | Domain | Create | {Entity}Service.cs implementation in Services/ |
| 7 | Infra | Modify | DbContext — add DbSet and OnModelCreating configuration |
| 8 | Infra | Create | {Entity}Repository.cs in Repository/ |
| 9 | Infra | Create | {External}AppService.cs in AppServices/ (if applicable) |
| 10 | Infra | Run | dotnet ef migrations add (if applicable) |
| 11 | Application | Modify | Startup.cs — register repositories, AppServices, services |
tools
Guides how to integrate the zTools package for ChatGPT, DALL-E image generation, file upload (S3), slug generation, email sending, and document validation in a .NET 8 project. Use when the user wants to use AI features, upload files, generate slugs, send emails, or understand zTools integration.
documentation
Generates a comprehensive, standardized README.md for any project. Use when the user wants to create or regenerate a README file following the project's documentation standard.
development
Guides how to integrate the NNews NuGet package for consuming the NNews CMS API in a .NET 8 project. Use when the user wants to consume articles, categories, tags, images, or AI-powered content generation from the NNews API.
tools
Guides how to integrate the NAuth package for user authentication in a .NET 8 project. Use when the user wants to add authentication, configure NAuth, use IUserClient, or understand the NAuth authentication flow.