C# 14 Mastery Series Part 8: Real-World Implementation – Building Production Applications

C# 14 Mastery Series Part 8: Real-World Implementation – Building Production Applications

Welcome to the final part of our C# 14 mastery series! After seven comprehensive parts covering features, performance, and migration strategies, it’s time to bring everything together. Today we’ll build complete, production-ready applications that demonstrate the full power of C# 14, showcase advanced architectural patterns, and provide real-world implementation examples you can apply immediately.

Complete E-Commerce Application

Domain Models with Field-Backed Properties

public class Product
{
    public int Id { get; set; }
    
    // Field-backed properties with validation and transformation
    public string Name
    {
        get => field;
        set => field = string.IsNullOrWhiteSpace(value) 
            ? throw new ArgumentException("Product name cannot be empty")
            : value.Trim();
    }
    
    public decimal Price
    {
        get => field;
        set => field = value >= 0 
            ? Math.Round(value, 2, MidpointRounding.AwayFromZero)
            : throw new ArgumentException("Price cannot be negative");
    }
    
    public int StockQuantity
    {
        get => field;
        set
        {
            if (value < 0) throw new ArgumentException("Stock cannot be negative");
            var oldValue = field;
            field = value;
            
            if (oldValue > 0 && value == 0)
                OnStockDepleted();
        }
    }
    
    public string Slug
    {
        get => field ??= GenerateSlug(Name);
        set => field = value;
    }
    
    public event EventHandler StockDepleted;
    
    private static string GenerateSlug(string name) =>
        name?.ToLowerInvariant().Replace(" ", "-") ?? string.Empty;
        
    private void OnStockDepleted() => StockDepleted?.Invoke(this, EventArgs.Empty);
}

Extension Properties for Business Logic

extension ProductExtensions for Product
{
    // Business logic properties
    public bool IsAvailable => this.StockQuantity > 0 && this.IsActive;
    public bool IsLowStock => this.StockQuantity > 0 && this.StockQuantity <= 5;
    public bool IsOnSale => this.SalePrice.HasValue && this.SalePrice < this.Price;
    
    // Display properties
    public string DisplayPrice => this.IsOnSale 
        ? $"${this.SalePrice:F2} (was ${this.Price:F2})"
        : $"${this.Price:F2}";
    
    public string StockStatus => this.StockQuantity switch
    {
        0 => "Out of Stock",
        var qty when qty <= 5 => $"Low Stock ({qty} remaining)",
        _ => "In Stock"
    };
    
    // Pricing calculations
    public decimal EffectivePrice => this.SalePrice ?? this.Price;
    public decimal? SavingsAmount => this.IsOnSale ? this.Price - this.SalePrice : null;
    
    // Static factory methods
    public static Product CreateSample(string name, decimal price) => new()
    {
        Name = name,
        Price = price,
        StockQuantity = 100,
        IsActive = true
    };
}

Advanced Repository Pattern

Generic Repository with Extensions

public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> AddAsync(T entity);
    Task<T> UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

extension RepositoryExtensions<T> for IRepository<T> where T : class
{
    // Query capabilities
    public bool HasData => this.GetAllAsync().Result.Any();
    public bool IsEmpty => !this.HasData;
    
    // Batch operations
    public async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities)
    {
        var results = new List<T>();
        foreach (var entity in entities)
        {
            results.Add(await this.AddAsync(entity));
        }
        return results;
    }
    
    // Caching capabilities
    private static readonly ConcurrentDictionary<string, object> _cache = new();
    
    public async Task<T> GetByIdCachedAsync(int id, TimeSpan? expiration = null)
    {
        var cacheKey = $"{nameof(T)}_{id}";
        var expirationTime = expiration ?? TimeSpan.FromMinutes(5);
        
        if (_cache.TryGetValue(cacheKey, out var cached) && 
            cached is CachedItem<T> item && 
            item.ExpiresAt > DateTime.UtcNow)
        {
            return item.Value;
        }
        
        var entity = await this.GetByIdAsync(id);
        if (entity != null)
        {
            _cache[cacheKey] = new CachedItem<T>
            {
                Value = entity,
                ExpiresAt = DateTime.UtcNow.Add(expirationTime)
            };
        }
        
        return entity;
    }
    
    // Static configuration
    public static int DefaultPageSize { get; set; } = 20;
    public static int MaxPageSize { get; set; } = 100;
    
    // Pagination
    public async Task<PagedResult<T>> GetPagedAsync(int page = 1, int pageSize = 0)
    {
        pageSize = pageSize <= 0 ? DefaultPageSize : Math.Min(pageSize, MaxPageSize);
        
        var allItems = await this.GetAllAsync();
        var items = allItems.Skip((page - 1) * pageSize).Take(pageSize);
        var totalCount = allItems.Count();
        
        return new PagedResult<T>
        {
            Items = items.ToList(),
            CurrentPage = page,
            PageSize = pageSize,
            TotalCount = totalCount,
            TotalPages = (int)Math.Ceiling((double)totalCount / pageSize)
        };
    }
}

Modern API Controllers

RESTful API with C# 14

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IRepository<Product> _repository;
    private readonly ILogger<ProductsController> _logger;
    
    public ProductsController(IRepository<Product> repository, ILogger<ProductsController> logger)
    {
        _repository = repository;
        _logger = logger;
    }
    
    [HttpGet]
    public async Task<ActionResult<ApiResponse<IEnumerable<ProductViewModel>>>> GetProducts(
        [FromQuery] int page = 1, 
        [FromQuery] int pageSize = 20)
    {
        try
        {
            var pagedResult = await _repository.GetPagedAsync(page, pageSize);
            var viewModels = pagedResult.Items.Select(p => p.ToViewModel());
            
            return Ok(ApiResponse<IEnumerable<ProductViewModel>>.Success(
                viewModels, 
                $"Retrieved {pagedResult.Items.Count} of {pagedResult.TotalCount} products"));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving products");
            return StatusCode(500, ApiResponse<IEnumerable<ProductViewModel>>.Error("An error occurred"));
        }
    }
    
    [HttpGet("{id:int}")]
    public async Task<ActionResult<ApiResponse<ProductDetailViewModel>>> GetProduct(int id)
    {
        var product = await _repository.GetByIdCachedAsync(id);
        
        if (product == null)
            return NotFound(ApiResponse<ProductDetailViewModel>.Error("Product not found"));
        
        var viewModel = product.ToDetailViewModel();
        return Ok(ApiResponse<ProductDetailViewModel>.Success(viewModel));
    }
    
    [HttpPost]
    public async Task<ActionResult<ApiResponse<ProductViewModel>>> CreateProduct([FromBody] CreateProductRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ApiResponse<ProductViewModel>.Error("Invalid product data"));
        
        try
        {
            var product = request.ToProduct();
            var created = await _repository.AddAsync(product);
            var viewModel = created.ToViewModel();
            
            return CreatedAtAction(nameof(GetProduct), new { id = created.Id }, 
                                 ApiResponse<ProductViewModel>.Success(viewModel));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error creating product");
            return StatusCode(500, ApiResponse<ProductViewModel>.Error("An error occurred"));
        }
    }
}

// Mapping extensions
extension ProductMappingExtensions for Product
{
    public ProductViewModel ToViewModel() => new()
    {
        Id = this.Id,
        Name = this.Name,
        Price = this.DisplayPrice,
        IsAvailable = this.IsAvailable,
        StockStatus = this.StockStatus
    };
    
    public ProductDetailViewModel ToDetailViewModel() => new()
    {
        Id = this.Id,
        Name = this.Name,
        Description = this.Description,
        Price = this.DisplayPrice,
        OriginalPrice = this.IsOnSale ? this.Price : null,
        IsAvailable = this.IsAvailable,
        StockStatus = this.StockStatus
    };
}

extension CreateProductRequestExtensions for CreateProductRequest
{
    public Product ToProduct() => new()
    {
        Name = this.Name,
        Description = this.Description,
        Price = this.Price,
        StockQuantity = this.StockQuantity,
        IsActive = true,
        CreatedAt = DateTime.UtcNow
    };
}

Configuration with Field-Backed Properties

public class DatabaseOptions
{
    public string ConnectionString
    {
        get => field;
        set => field = string.IsNullOrWhiteSpace(value) 
            ? throw new ArgumentException("Connection string cannot be empty")
            : value;
    }
    
    public TimeSpan CommandTimeout
    {
        get => field == TimeSpan.Zero ? TimeSpan.FromSeconds(30) : field;
        set => field = value > TimeSpan.Zero 
            ? value 
            : throw new ArgumentException("Command timeout must be positive");
    }
    
    public int MaxRetryAttempts
    {
        get => field == 0 ? 3 : field;
        set => field = Math.Max(0, Math.Min(value, 10));
    }
}

public class CacheOptions
{
    public TimeSpan DefaultExpiration
    {
        get => field == TimeSpan.Zero ? TimeSpan.FromMinutes(5) : field;
        set => field = value;
    }
    
    public int MaxCacheSize
    {
        get => field == 0 ? 1000 : field;
        set => field = Math.Max(0, value);
    }
    
    public bool EnableDistributedCache
    {
        get => field;
        set => field = value;
    }
}

Dependency Injection Setup

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Repository registration using nameof for type-safe logging
        services.AddRepositories(typeof(Product), typeof(Order), typeof(Customer));
        
        // Configure options
        services.Configure<DatabaseOptions>(Configuration.GetSection("Database"));
        services.Configure<CacheOptions>(Configuration.GetSection("Cache"));
        
        // Add application services
        services.AddApplicationServices();
        
        // Add API services
        services.AddControllers();
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
    }
}

extension ServiceCollectionExtensions for IServiceCollection
{
    public IServiceCollection AddRepositories(params Type[] entityTypes)
    {
        foreach (var entityType in entityTypes)
        {
            var repositoryInterface = typeof(IRepository<>).MakeGenericType(entityType);
            var repositoryImplementation = typeof(Repository<>).MakeGenericType(entityType);
            
            this.AddScoped(repositoryInterface, repositoryImplementation);
        }
        
        return this;
    }
    
    public IServiceCollection AddApplicationServices()
    {
        this.AddScoped<ProductService>();
        this.AddScoped<OrderService>();
        this.AddScoped<CustomerService>();
        this.AddScoped<ICacheService, CacheService>();
        
        return this;
    }
    
    // Type-safe service registration using nameof
    public IServiceCollection AddTypedService<TInterface, TImplementation>()
        where TImplementation : class, TInterface
        where TInterface : class
    {
        this.AddScoped<TInterface, TImplementation>();
        
        var logger = this.BuildServiceProvider().GetService<ILogger<Startup>>();
        logger?.LogInformation("Registered {Interface} with {Implementation}", 
                              nameof(TInterface), nameof(TImplementation));
        
        return this;
    }
}

Testing with C# 14 Features

public class ProductTests
{
    [Fact]
    public void Product_Name_ShouldTrimWhitespace()
    {
        // Arrange
        var product = new Product();
        
        // Act
        product.Name = "  Test Product  ";
        
        // Assert
        Assert.Equal("Test Product", product.Name);
    }
    
    [Fact]
    public void Product_Price_ShouldRoundToTwoDecimals()
    {
        // Arrange
        var product = new Product();
        
        // Act
        product.Price = 19.999m;
        
        // Assert
        Assert.Equal(20.00m, product.Price);
    }
    
    [Fact]
    public void Product_IsAvailable_ShouldReturnTrue_WhenInStock()
    {
        // Arrange
        var product = new Product
        {
            StockQuantity = 10,
            IsActive = true
        };
        
        // Act & Assert
        Assert.True(product.IsAvailable);
    }
    
    [Fact]
    public void Repository_GetPagedAsync_ShouldReturnCorrectPage()
    {
        // Arrange
        var repository = new Mock<IRepository<Product>>();
        var products = GenerateTestProducts(50);
        repository.Setup(r => r.GetAllAsync()).ReturnsAsync(products);
        
        // Act
        var result = repository.Object.GetPagedAsync(2, 10).Result;
        
        // Assert
        Assert.Equal(2, result.CurrentPage);
        Assert.Equal(10, result.PageSize);
        Assert.Equal(50, result.TotalCount);
        Assert.Equal(5, result.TotalPages);
        Assert.Equal(10, result.Items.Count);
    }
    
    [Theory]
    [InlineData("laptop", 1299.99, true)]
    [InlineData("", 100.00, false)] // Empty name should throw
    [InlineData("mouse", -50.00, false)] // Negative price should throw
    public void Product_Creation_ShouldValidateInputs(string name, decimal price, bool shouldSucceed)
    {
        // Act & Assert
        if (shouldSucceed)
        {
            var product = new Product { Name = name, Price = price };
            Assert.Equal(name, product.Name);
            Assert.Equal(price, product.Price);
        }
        else
        {
            Assert.ThrowsAny<ArgumentException>(() =>
            {
                var product = new Product { Name = name, Price = price };
            });
        }
    }
    
    private static List<Product> GenerateTestProducts(int count)
    {
        return Enumerable.Range(1, count)
            .Select(i => Product.CreateSample($"Product {i}", i * 10))
            .ToList();
    }
}

Production Deployment Considerations

Performance Monitoring

public class PerformanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<PerformanceMiddleware> _logger;
    
    public PerformanceMiddleware(RequestDelegate next, ILogger<PerformanceMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();
            
            if (stopwatch.ElapsedMilliseconds > 1000) // Log slow requests
            {
                _logger.LogWarning("Slow request detected: {Method} {Path} took {ElapsedMs}ms",
                    context.Request.Method,
                    context.Request.Path,
                    stopwatch.ElapsedMilliseconds);
            }
        }
    }
}

// Extension properties for monitoring
extension HttpContextExtensions for HttpContext
{
    public bool IsApiRequest => this.Request.Path.StartsWithSegments("/api");
    public bool IsHealthCheck => this.Request.Path.StartsWithSegments("/health");
    public string RequestIdentifier => $"{this.Request.Method} {this.Request.Path}";
    
    // Performance tracking
    public void TrackPerformance(string operationName, TimeSpan duration)
    {
        this.Items[$"Performance_{operationName}"] = duration;
    }
    
    public TimeSpan? GetPerformance(string operationName)
    {
        return this.Items.TryGetValue($"Performance_{operationName}", out var value) 
            ? value as TimeSpan? 
            : null;
    }
}

Key Takeaways and Best Practices

From building this complete application with C# 14, here are the essential takeaways:

Field-Backed Properties Best Practices

  • Validation in Setters: Perform validation and transformation when data is set, not when read
  • Lazy Loading: Use null-coalescing assignment for expensive computations
  • Event Triggers: Integrate business events directly into property setters
  • Performance: Avoid expensive operations in getters

Extension Members Best Practices

  • Logical Grouping: Organize related functionality in single extension blocks
  • Static Utilities: Include helper methods and factory functions
  • Computed Properties: Create intuitive, business-focused property names
  • Caching: Use static caching for expensive computations

Generic Type Enhancements

  • Type Safety: Use nameof with unbound generics for refactoring safety
  • Logging: Eliminate hardcoded type names in logs and error messages
  • Configuration: Create type-safe configuration registration
  • Performance: Zero runtime overhead compared to hardcoded strings

Conclusion

C# 14 represents a significant evolution in the language, providing developers with powerful tools to write more maintainable, performant, and expressive code. Through this comprehensive series, we've explored how these features work individually and, more importantly, how they work together to solve real-world problems.

The complete e-commerce application we built demonstrates that C# 14 features aren't just syntactic sugar—they enable better software architecture, cleaner code organization, and improved developer productivity. Field-backed properties reduce boilerplate while adding functionality, extension members create more intuitive APIs, and enhanced generic support provides better type safety.

As you adopt C# 14 in your projects, remember that the best implementations combine multiple features thoughtfully. Start small, measure the impact, and gradually expand your usage as your team becomes comfortable with the new capabilities.

The future of C# development is exciting, and C# 14 provides a solid foundation for building the next generation of applications. Happy coding!

Thank you for following along with the C# 14 Mastery Series!

Navigate<< C# 14 Mastery Series Part 7: Migration Strategies – From C# 13 to C# 14

Written by:

265 Posts

View All Posts
Follow Me :