C# Coding Standard

C# coding standard for Cadtastic Solutions.

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

Architecture and Design Standards

Purpose

This document defines architectural principles and design patterns to ensure:

Core Principles

SOLID Principles

  1. Single Responsibility Principle (SRP)
// 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
    }
}
  1. Open/Closed Principle (OCP)
// 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
        }
    }
}
  1. Liskov Substitution Principle (LSP)
// 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
    }
}
  1. Interface Segregation Principle (ISP)
// 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
}
  1. Dependency Inversion Principle (DIP)
// 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();
    }
}

Architectural Patterns

Clean Architecture

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

Example Implementation

// 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
    }
}

Design Patterns

Creational Patterns

Factory Pattern

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}")
    };
}

Builder Pattern

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;
}

Behavioral Patterns

Strategy Pattern

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
    }
}

Observer Pattern

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);
        }
    }
}

Cross-Cutting Concerns

Logging

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;
        }
    }
}

Exception Handling

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;
    }
}

Caching

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;
    }
}

Performance Considerations

Async/Await Best Practices

// 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());
}

Memory Management

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);
    }
}

Security Guidelines

Input Validation

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
    }
}

Authorization

[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
    }
}

Additional Resources