From backend-csharp
Use when designing or implementing a 3-layer (Presentation / Business Logic / Data Access) architecture in a C# .NET project. Triggers when scaffolding a new project, adding layers, defining service interfaces, or reviewing layer boundary violations.
How this skill is triggered — by the user, by Claude, or both
Slash command
/backend-csharp:csharp-layered-architectureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Implement a strict 3-layer architecture — Presentation, Business Logic (Service), and Data Access — with clear dependency direction: Presentation → BLL → DAL. No layer may reference a layer above it.
Implement a strict 3-layer architecture — Presentation, Business Logic (Service), and Data Access — with clear dependency direction: Presentation → BLL → DAL. No layer may reference a layer above it.
┌─────────────────────────────────┐
│ Presentation Layer (UI/API) │ Controllers, Views, DTOs, Request/Response models
├─────────────────────────────────┤
│ Business Logic Layer (BLL) │ Services, domain rules, orchestration, validation
├─────────────────────────────────┤
│ Data Access Layer (DAL) │ Repositories, DbContext, Entity models, migrations
└─────────────────────────────────┘
Dependency rule: Each layer depends only on the layer directly below it. Interfaces defined in BLL; implemented in DAL.
MyApp.sln
├── src/
│ ├── MyApp.API/ ← Presentation Layer
│ │ ├── Controllers/
│ │ ├── Models/ # Request/Response DTOs
│ │ ├── Filters/
│ │ └── Program.cs
│ │
│ ├── MyApp.BLL/ ← Business Logic Layer
│ │ ├── Interfaces/
│ │ │ ├── IOrderService.cs
│ │ │ └── IProductService.cs
│ │ ├── Services/
│ │ │ ├── OrderService.cs
│ │ │ └── ProductService.cs
│ │ ├── DTOs/ # Shared data transfer objects
│ │ ├── Exceptions/ # Domain-specific exceptions
│ │ └── Validators/ # FluentValidation validators
│ │
│ └── MyApp.DAL/ ← Data Access Layer
│ ├── Context/
│ │ └── AppDbContext.cs
│ ├── Entities/ # EF Core entity models
│ ├── Repositories/
│ │ ├── Interfaces/
│ │ │ └── IOrderRepository.cs
│ │ └── OrderRepository.cs
│ ├── Configurations/ # EF Core IEntityTypeConfiguration<T>
│ └── Migrations/
│
└── tests/
├── MyApp.BLL.Tests/
└── MyApp.API.Tests/
// MyApp.DAL/Repositories/Interfaces/IOrderRepository.cs
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id, CancellationToken ct = default);
Task<IReadOnlyList<Order>> GetAllAsync(CancellationToken ct = default);
Task<Order> AddAsync(Order entity, CancellationToken ct = default);
Task UpdateAsync(Order entity, CancellationToken ct = default);
Task DeleteAsync(int id, CancellationToken ct = default);
}
// MyApp.BLL/Interfaces/IOrderService.cs
public interface IOrderService
{
Task<OrderDto> GetByIdAsync(int id, CancellationToken ct = default);
Task<IReadOnlyList<OrderDto>> GetAllAsync(CancellationToken ct = default);
Task<OrderDto> CreateAsync(CreateOrderDto dto, CancellationToken ct = default);
Task UpdateAsync(int id, UpdateOrderDto dto, CancellationToken ct = default);
Task DeleteAsync(int id, CancellationToken ct = default);
}
Service layer: orchestrates business logic, maps entities to DTOs, enforces rules. Never exposes DAL entities to the Presentation layer.
// MyApp.BLL/Services/OrderService.cs
public sealed class OrderService : IOrderService
{
private readonly IOrderRepository _orderRepo;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository orderRepo, ILogger<OrderService> logger)
{
_orderRepo = orderRepo;
_logger = logger;
}
public async Task<OrderDto> GetByIdAsync(int id, CancellationToken ct = default)
{
var order = await _orderRepo.GetByIdAsync(id, ct)
?? throw new NotFoundException($"Order {id} not found.");
return MapToDto(order);
}
public async Task<OrderDto> CreateAsync(CreateOrderDto dto, CancellationToken ct = default)
{
// Business rule enforcement
if (dto.Quantity <= 0)
throw new ValidationException("Quantity must be positive.");
var entity = new Order
{
CustomerName = dto.CustomerName,
Quantity = dto.Quantity,
CreatedAt = DateTime.UtcNow
};
var created = await _orderRepo.AddAsync(entity, ct);
_logger.LogInformation("Order {OrderId} created for {Customer}", created.Id, created.CustomerName);
return MapToDto(created);
}
private static OrderDto MapToDto(Order order) => new()
{
Id = order.Id,
CustomerName = order.CustomerName,
Quantity = order.Quantity,
CreatedAt = order.CreatedAt
};
}
Register all services in Program.cs or a dedicated extension method. Keep DAL implementations hidden behind interfaces:
// Program.cs
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<IProductService, ProductService>();
// Or via extension method (preferred for larger projects):
builder.Services.AddBllServices();
builder.Services.AddDalServices(builder.Configuration);
// MyApp.BLL/DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddBllServices(this IServiceCollection services)
{
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IProductService, ProductService>();
return services;
}
}
| Allowed | Not Allowed |
|---|---|
API controller references IOrderService (BLL interface) | Controller references IOrderRepository (DAL) directly |
BLL service references IOrderRepository (DAL interface) | BLL references AppDbContext directly |
DAL implements IOrderRepository | DAL service layer logic (business rules in DAL) |
| DTOs shared across layers | DAL entities (Order) passed to controller |
| BLL defines domain exceptions | API layer defines business rules |
MyApp.DAL/Entities/): EF Core model classes, only cross DAL↔BLLMyApp.BLL/DTOs/): plain data bags for BLL↔API, no EF annotationsMyApp.API/Models/): API contract shapes, no BLL logic// Entity (DAL) — has EF Core mapping
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; } = string.Empty;
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public ICollection<OrderItem> Items { get; set; } = [];
}
// DTO (BLL) — crosses BLL/API boundary, no EF annotations
public record OrderDto(int Id, string CustomerName, int Quantity, DateTime CreatedAt);
// Request model (API) — user input binding
public class CreateOrderRequest
{
[Required] public string CustomerName { get; set; } = string.Empty;
[Range(1, 1000)] public int Quantity { get; set; }
}
MyApp.API, MyApp.BLL, MyApp.DALMyApp.API references MyApp.BLL only (not MyApp.DAL)MyApp.BLL references MyApp.DAL through interfaces onlyMyApp.DAL/Repositories/Interfaces/MyApp.BLL/Interfaces/NotFoundException, ValidationException, etc.)npx claudepluginhub gagandeepp/software-agent-teams --plugin backend-csharpGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.