C# coding standard for Cadtastic Solutions.
View the Project on GitHub Cadtastic-Solutions/C-Sharp-Coding-Standard
This document defines architectural principles and design patterns to ensure:
// Good - Single responsibility
public class OrderProcessor
{
private readonly IPaymentGateway _paymentGateway;
private readonly IOrderRepository _orderRepository;
public async Task<OrderResult> ProcessOrder(Order order)
{
// Only handles order processing logic
}
}
// Bad - Multiple responsibilities
public class Order
{
public void ProcessOrder()
{
// Handles payment processing
// Manages inventory
// Sends emails
// Updates database
}
}
// Good - Open for extension
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessPayment(Payment payment);
}
public class CreditCardProcessor : IPaymentProcessor { }
public class PayPalProcessor : IPaymentProcessor { }
public class CryptoProcessor : IPaymentProcessor { }
// Bad - Closed for extension
public class PaymentProcessor
{
public Task<PaymentResult> ProcessPayment(Payment payment)
{
switch (payment.Type)
{
case "CreditCard": // Handle credit card
case "PayPal": // Handle PayPal
// Adding new payment types requires modifying existing code
}
}
}
// Good - Derived classes are substitutable
public abstract class Bird
{
public virtual void Eat() { }
}
public class FlyingBird : Bird
{
public virtual void Fly() { }
}
public class Penguin : Bird
{
// Penguin doesn't try to implement Fly()
}
// Bad - Violates LSP
public abstract class Bird
{
public abstract void Fly();
}
public class Penguin : Bird
{
public override void Fly()
{
throw new NotSupportedException(); // Violates LSP
}
}
// Good - Segregated interfaces
public interface IReadRepository<T>
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
}
public interface IWriteRepository<T>
{
Task<T> CreateAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
// Bad - Fat interface
public interface IRepository<T>
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> CreateAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
Task<IEnumerable<T>> SearchAsync(string term);
Task<int> CountAsync();
Task<bool> ExistsAsync(int id);
// Too many responsibilities in one interface
}
// Good - Depends on abstractions
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository repository, ILogger<OrderService> logger)
{
_repository = repository;
_logger = logger;
}
}
// Bad - Depends on concrete implementations
public class OrderService
{
private readonly SqlOrderRepository _repository;
private readonly FileLogger _logger;
public OrderService()
{
_repository = new SqlOrderRepository();
_logger = new FileLogger();
}
}
ProjectName/
├── Core/ # Enterprise business rules
│ ├── Entities/ # Business entities
│ ├── Interfaces/ # Core interfaces
│ └── Services/ # Core business logic
│
├── Application/ # Application business rules
│ ├── DTOs/ # Data transfer objects
│ ├── Interfaces/ # Application interfaces
│ └── Services/ # Application services
│
├── Infrastructure/ # External concerns
│ ├── Data/ # Database implementation
│ ├── External/ # External services
│ └── Identity/ # Authentication/Authorization
│
└── Presentation/ # UI layer
├── API/ # REST API
├── Web/ # Web UI
└── Mobile/ # Mobile UI
// Core Layer
public class Order
{
public int Id { get; private set; }
public string Status { get; private set; }
public void Submit()
{
// Core business logic
}
}
// Application Layer
public class OrderService : IOrderService
{
private readonly IOrderRepository _repository;
public async Task<OrderDto> SubmitOrder(CreateOrderDto dto)
{
// Application logic
}
}
// Infrastructure Layer
public class SqlOrderRepository : IOrderRepository
{
private readonly DbContext _context;
public async Task<Order> GetById(int id)
{
// Data access logic
}
}
// Presentation Layer
[ApiController]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public async Task<IActionResult> Submit([FromBody] CreateOrderDto dto)
{
// API endpoint logic
}
}
public interface IDocumentFactory
{
IDocument CreateDocument(string type);
}
public class DocumentFactory : IDocumentFactory
{
public IDocument CreateDocument(string type) => type switch
{
"PDF" => new PdfDocument(),
"Word" => new WordDocument(),
_ => throw new ArgumentException($"Unknown document type: {type}")
};
}
public class EmailBuilder
{
private readonly Email _email = new();
public EmailBuilder WithSubject(string subject)
{
_email.Subject = subject;
return this;
}
public EmailBuilder WithBody(string body)
{
_email.Body = body;
return this;
}
public Email Build() => _email;
}
public interface IDiscountStrategy
{
decimal CalculateDiscount(Order order);
}
public class VolumeDiscountStrategy : IDiscountStrategy
{
public decimal CalculateDiscount(Order order)
{
// Volume-based discount logic
}
}
public class LoyaltyDiscountStrategy : IDiscountStrategy
{
public decimal CalculateDiscount(Order order)
{
// Loyalty-based discount logic
}
}
public interface IOrderObserver
{
void OnOrderSubmitted(Order order);
}
public class OrderService
{
private readonly List<IOrderObserver> _observers = new();
public void AddObserver(IOrderObserver observer)
{
_observers.Add(observer);
}
public async Task SubmitOrder(Order order)
{
// Process order
foreach (var observer in _observers)
{
observer.OnOrderSubmitted(order);
}
}
}
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public async Task<Order> ProcessOrder(Order order)
{
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = order.Id,
["CustomerId"] = order.CustomerId
});
try
{
_logger.LogInformation("Processing order {OrderId}", order.Id);
// Process order
_logger.LogInformation("Order {OrderId} processed successfully", order.Id);
return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing order {OrderId}", order.Id);
throw;
}
}
}
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public async ValueTask<bool> TryHandleAsync(
HttpContext context,
Exception exception,
CancellationToken cancellationToken)
{
var (statusCode, message) = exception switch
{
NotFoundException => (StatusCodes.Status404NotFound, "Resource not found"),
ValidationException => (StatusCodes.Status400BadRequest, "Validation failed"),
_ => (StatusCodes.Status500InternalServerError, "An error occurred")
};
context.Response.StatusCode = statusCode;
await context.Response.WriteAsJsonAsync(new { message }, cancellationToken);
return true;
}
}
public class CachedOrderRepository : IOrderRepository
{
private readonly IOrderRepository _repository;
private readonly IDistributedCache _cache;
public async Task<Order> GetByIdAsync(int id)
{
var cacheKey = $"order:{id}";
var cached = await _cache.GetAsync<Order>(cacheKey);
if (cached != null) return cached;
var order = await _repository.GetByIdAsync(id);
await _cache.SetAsync(cacheKey, order, TimeSpan.FromMinutes(10));
return order;
}
}
// Good - Proper async/await usage
public async Task<IEnumerable<OrderDto>> GetOrdersAsync()
{
var orders = await _repository.GetOrdersAsync();
return orders.Select(o => o.ToDto());
}
// Bad - Blocking async code
public IEnumerable<OrderDto> GetOrders()
{
var orders = _repository.GetOrdersAsync().Result; // Can cause deadlocks
return orders.Select(o => o.ToDto());
}
public class LargeDataProcessor : IDisposable
{
private bool _disposed;
private readonly MemoryStream _buffer;
public async Task ProcessLargeFile(Stream inputStream)
{
using var reader = new StreamReader(inputStream);
while (!reader.EndOfStream)
{
// Process in chunks to manage memory
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_buffer?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public class UserService
{
public async Task<User> CreateUser(CreateUserDto dto)
{
Guard.Against.NullOrEmpty(dto.Username, nameof(dto.Username));
Guard.Against.NullOrEmpty(dto.Email, nameof(dto.Email));
if (!IsValidEmail(dto.Email))
throw new ValidationException("Invalid email format");
// Additional validation and processing
}
}
[Authorize]
public class OrdersController : ControllerBase
{
[RequiresPermission(Permissions.ManageOrders)]
public async Task<IActionResult> CancelOrder(int id)
{
var user = User.GetUserId();
var order = await _orderService.GetOrderAsync(id);
if (order.CustomerId != user && !User.IsInRole(Roles.Admin))
return Forbid();
// Process cancellation
}
}