C# Coding Standard

C# coding standard for Cadtastic Solutions.

View the Project on GitHub Cadtastic-Solutions/C-Sharp-Coding-Standard

Clean Architecture Project Structure

Purpose

This standard defines a robust, scalable structure for complex applications using Clean Architecture principles. This structure ensures:

Solution Structure

Solution/                                # Solution root
├── ProjectName.Core/                    # Domain Layer - Enterprise Business Rules
├── ProjectName.Application/             # Application Layer - Application Business Rules
├── ProjectName.Infrastructure/          # Infrastructure Layer - External Concerns
├── ProjectName.Api/                     # Presentation Layer - API Interface
├── ProjectName.Web/                     # Presentation Layer - Web Interface
├── ProjectName.Mobile/                  # Presentation Layer - Mobile Interface
├── ProjectName.Tests/                   # Test Projects
│   ├── Core.Tests/                   
│   ├── Application.Tests/            
│   ├── Infrastructure.Tests/         
│   ├── Api.Tests/                    
│   └── Integration.Tests/     
├── .editorconfig                        # Solution-wide editor configuration
├── .gitignore                          # Solution-wide git ignore rules
├── Directory.Build.props                # Solution-wide build properties
├── README.md                           # Solution overview
└── Solution.sln                        # Solution file

Project Dependencies

flowchart LR
    Core["ProjectName.Core"]
    App["ProjectName.Application"]
    Infra["ProjectName.Infrastructure"]
    Api["ProjectName.Api"]
    Web["ProjectName.Web"]
    Mobile["ProjectName.Mobile"]

    Core --> App
    Core --> Infra
    App --> Infra
    App --> Api
    App --> Web
    App --> Mobile

    style Core fill:#2d3748,stroke:#1a202c,stroke-width:2px,color:#ffffff
    style App fill:#3182ce,stroke:#2c5282,stroke-width:2px,color:#ffffff
    style Infra fill:#38a169,stroke:#2f855a,stroke-width:2px,color:#ffffff
    style Api fill:#d53f8c,stroke:#b83280,stroke-width:2px,color:#ffffff
    style Web fill:#d53f8c,stroke:#b83280,stroke-width:2px,color:#ffffff
    style Mobile fill:#d53f8c,stroke:#b83280,stroke-width:2px,color:#ffffff

Individual Project Structures

1. ProjectName.Core

ProjectName.Core/                  # Domain Layer
├── Entities/                     # Business entities
│   ├── Order.cs
│   └── Customer.cs
├── ValueObjects/                 # Value objects
│   ├── Money.cs
│   └── Address.cs
├── Interfaces/                   # Core interfaces
│   ├── Repositories/
│   └── Services/
├── Events/                      # Domain events
│   ├── OrderCreated.cs
│   └── OrderCancelled.cs
├── Exceptions/                  # Custom exceptions
├── Enums/                      # Domain enumerations
└── Services/                   # Domain services

2. ProjectName.Application

ProjectName.Application/           # Application Layer
├── Common/                      # Shared components
│   ├── Behaviors/
│   └── Exceptions/
├── DTOs/                       # Data transfer objects
│   ├── Requests/
│   └── Responses/
├── Interfaces/                 # Application interfaces
├── Services/                   # Application services
├── Validators/                 # Request validators
├── Mappings/                   # Object mappers
└── Extensions/                 # Extension methods

3. ProjectName.Infrastructure

ProjectName.Infrastructure/        # Infrastructure Layer
├── Data/                        # Data access
│   ├── Context/
│   ├── Configurations/
│   ├── Migrations/
│   └── Repositories/
├── External/                    # External services
│   ├── APIs/
│   └── Providers/
├── Identity/                   # Authentication/Authorization
├── Logging/                    # Logging implementations
├── Messaging/                  # Message queues/buses
└── Storage/                    # File storage

4. Presentation Projects (API/Web/Mobile)

ProjectName.[Api/Web/Mobile]/     # Presentation Layer
├── Controllers/                 # API controllers
├── Filters/                    # Request filters
├── Models/                     # View models
├── Middleware/                 # Custom middleware
└── Extensions/                 # Extension methods

Implementation Examples

1. Core Layer

// Core/Entities/Order.cs
public class Order : IAggregateRoot
{
    private readonly List<OrderItem> _items = new();
    
    public Guid Id { get; private set; }
    public OrderStatus Status { get; private set; }
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();

    public void AddItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOrderStateException("Can only add items to draft orders");
            
        _items.Add(new OrderItem(product, quantity));
    }

    public decimal CalculateTotal() => _items.Sum(i => i.GetSubtotal());
}

// Core/Interfaces/Repositories/IOrderRepository.cs
public interface IOrderRepository : IRepository<Order>
{
    Task<Order> GetByIdWithItemsAsync(Guid id);
    Task<IEnumerable<Order>> GetByCustomerAsync(Guid customerId);
}

2. Application Layer

// Application/Services/OrderService.cs
public class OrderService : IOrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IUnitOfWork _unitOfWork;

    public async Task<OrderDto> CreateOrder(CreateOrderDto request)
    {
        var order = new Order(request.CustomerId);
        
        foreach (var item in request.Items)
            order.AddItem(item.ProductId, item.Quantity);

        await _orderRepository.AddAsync(order);
        await _unitOfWork.SaveChangesAsync();

        return order.ToDto();
    }
}

// Application/DTOs/Requests/CreateOrderDto.cs
public class CreateOrderDto
{
    public Guid CustomerId { get; set; }
    public List<OrderItemDto> Items { get; set; }
}

3. Infrastructure Layer

// Infrastructure/Data/Repositories/OrderRepository.cs
public class OrderRepository : Repository<Order>, IOrderRepository
{
    public OrderRepository(ApplicationDbContext context) : base(context)
    {
    }

    public async Task<Order> GetByIdWithItemsAsync(Guid id)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id);
    }
}

// Infrastructure/External/EmailService.cs
public class EmailService : IEmailService
{
    private readonly IEmailSettings _settings;

    public async Task SendOrderConfirmation(Order order)
    {
        // Implementation using real email service
    }
}

4. Presentation Layer

// Api/Controllers/OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;

    [HttpPost]
    public async Task<ActionResult<OrderDto>> Create(CreateOrderDto request)
    {
        var order = await _orderService.CreateOrder(request);
        return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
    }
}

Best Practices

1. Dependency Injection

// Infrastructure/DependencyInjection.cs
public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services, 
        IConfiguration configuration)
    {
        services.AddDbContext<ApplicationDbContext>();
        services.AddScoped<IOrderRepository, OrderRepository>();
        services.AddScoped<IEmailService, EmailService>();
        
        return services;
    }
}

2. Validation

// Application/Validators/CreateOrderValidator.cs
public class CreateOrderValidator : AbstractValidator<CreateOrderDto>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Items).NotEmpty();
        RuleForEach(x => x.Items).SetValidator(new OrderItemValidator());
    }
}

3. Cross-Cutting Concerns

// Application/Behaviors/LoggingBehavior.cs
public class LoggingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public async Task<TResponse> Handle(
        TRequest request, 
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Handling {RequestName}", typeof(TRequest).Name);
        var response = await next();
        _logger.LogInformation("Handled {RequestName}", typeof(TRequest).Name);
        return response;
    }
}

Testing Approach

// Core.Tests/Entities/OrderTests.cs
public class OrderTests
{
    [Fact]
    public void AddItem_WhenOrderIsDraft_ShouldAddItem()
    {
        // Arrange
        var order = new Order();
        var product = new Product("Test", 10m);

        // Act
        order.AddItem(product, 1);

        // Assert
        Assert.Single(order.Items);
    }
}

// Application.Tests/Services/OrderServiceTests.cs
public class OrderServiceTests
{
    private readonly Mock<IOrderRepository> _orderRepository;
    private readonly IOrderService _orderService;

    [Fact]
    public async Task CreateOrder_WithValidRequest_ShouldCreateOrder()
    {
        // Arrange
        var request = new CreateOrderDto();

        // Act
        var result = await _orderService.CreateOrder(request);

        // Assert
        _orderRepository.Verify(x => x.AddAsync(It.IsAny<Order>()), Times.Once);
    }
}

Migration Strategy

When migrating from a monolithic structure:

  1. Identify and extract domain entities and interfaces
  2. Move business logic to application services
  3. Implement infrastructure concerns
  4. Create new presentation layer
  5. Gradually move features to new structure

Additional Resources