C# coding standard for Cadtastic Solutions.
View the Project on GitHub Cadtastic-Solutions/C-Sharp-Coding-Standard
This standard defines a robust, scalable structure for complex applications using Clean Architecture principles. This structure ensures:
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
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
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
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
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
ProjectName.[Api/Web/Mobile]/ # Presentation Layer
├── Controllers/ # API controllers
├── Filters/ # Request filters
├── Models/ # View models
├── Middleware/ # Custom middleware
└── Extensions/ # Extension methods
// 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);
}
// 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; }
}
// 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
}
}
// 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);
}
}
// 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;
}
}
// 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());
}
}
// 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;
}
}
// 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);
}
}
When migrating from a monolithic structure: