.
NET Core Development Guidelines & Best
Practices
A Comprehensive Developer's Handbook
Version: 1.0 Last Updated: January 2026 Verified Against: Microsoft Official Documentation
Table of Contents
Part I: Core Development
● [Introduction](#chapter-1-introduction)
● [Naming Conventions](#chapter-2-naming-conventions)
● [C# Coding Conventions](#chapter-3-c-coding-conventions)
● [Type Selection Guide](#chapter-4-type-selection-guide)
● [Nullable Reference Types](#chapter-5-nullable-reference-types)
● [SOLID Principles](#chapter-6-solid-principles)
● [Clean Architecture](#chapter-7-clean-architecture)
● [[Link] Core Fundamentals](#chapter-8-aspnet-core-fundamentals)
● [Middleware Pipeline](#chapter-9-middleware-pipeline)
● [API Design](#chapter-10-api-design)
● [Async/Await Deep Dive](#chapter-11-asyncawait-deep-dive)
● [Entity Framework Core](#chapter-12-entity-framework-core)
● [Dependency Injection](#chapter-13-dependency-injection)
● [Error Handling](#chapter-14-error-handling)
● [Input Validation](#chapter-15-input-validation)
● [Security](#chapter-16-security)
● [Performance Optimization](#chapter-17-performance-optimization)
● [.NET 8/9 Modern Features](#chapter-18-net-89-modern-features)
● [Observability & Monitoring](#chapter-19-observability--monitoring)
● [Testing](#chapter-20-testing)
● [Code Review Checklist](#chapter-21-code-review-checklist)
Part II: CMMI Coding Practices (Level 3 & 5) 22. [Code Traceability](#chapter-22-code-traceability) 23.
[Commit Standards](#chapter-23-commit-standards) 24. [Static Code Analysis](#chapter-24-static-code-
analysis) 25. [Code Metrics & Quality Gates](#chapter-25-code-metrics--quality-gates) 26. [CMMI Peer
Review Standards](#chapter-26-cmmi-peer-review-standards) 27. [Defect Classification (ODC)](#chapter-27-
defect-classification-odc) 28. [Defect Prevention Practices](#chapter-28-defect-prevention-practices) 29.
[Statistical Code Quality (Level 4-5)](#chapter-29-statistical-code-quality-level-4-5)
Chapter 1: Introduction
Purpose of This Guideline
This document serves as a comprehensive guide for .NET Core developers, providing best practices, coding
standards, and architectural patterns that ensure code quality, maintainability, and performance. Whether you're
a junior developer starting your journey or a senior architect designing complex systems, this guideline will help
you write better .NET code.
Who Should Read This
● Junior Developers: Learn industry-standard practices from the start
● Mid-Level Developers: Solidify your understanding and fill knowledge gaps
● Senior Developers: Use as a reference and team standard
● Tech Leads: Enforce consistent coding standards across teams
● Architects: Reference for architectural decisions
.NET Versions Covered
This guideline covers:
● .NET 6 (LTS)
● .NET 7
● .NET 8 (LTS) - Current recommended version
● .NET 9
● .NET 10 (Preview features noted)
**Recommendation:** Use LTS (Long-Term Support) versions for production applications. As of
2026, .NET 8 is the recommended LTS version.
Chapter 2: Naming Conventions
2.1 Casing Rules
Consistent naming is crucial for code readability. The following table summarizes the casing rules for different
code elements:
Element Casing Example
Namespace PascalCase `[Link]`
Class PascalCase `CustomerService`
Interface IPascalCase `ICustomerRepository`
Method PascalCase `GetCustomerById`
Property PascalCase `FirstName`
Public Field PascalCase `MaxRetryCount`
Private Field _camelCase `_customerRepository`
Static Private Field s_camelCase `s_instance`
Thread Static Field t_camelCase `t_currentContext`
Constant PascalCase `DefaultTimeout`
Parameter camelCase `customerId`
Local Variable camelCase `orderTotal`
Enum PascalCase `OrderStatus`
Enum Value PascalCase `Pending`, `Completed`
Type Parameter TPascalCase `TEntity`, `TResult`
Async Method PascalCase + Async `GetCustomerByIdAsync`
2.2 Naming Best Practices
DO ✅
● Use meaningful, descriptive names
// GOOD: Clear intent
public class CustomerOrderProcessor { }
public decimal CalculateTotalWithTax(decimal subtotal, decimal taxRate) { }
private readonly IEmailService _emailService;
● Use nouns for classes and properties
// GOOD: Nouns describe "what it is"
public class Customer { }
public class OrderRepository { }
public string FirstName { get; set; }
● Use verbs for methods
// GOOD: Verbs describe "what it does"
public void SendEmail() { }
public Customer GetCustomerById(int id) { }
public async Task ProcessOrderAsync() { }
● Prefix interfaces with 'I'
// GOOD
public interface ICustomerRepository { }
public interface IEmailService { }
● Suffix async methods with 'Async'
// GOOD
public async Task<Customer> GetCustomerAsync(int id) { }
public async Task SendEmailAsync(string to, string subject) { }
DON'T ❌
● Don't use Hungarian notation
// BAD: Hungarian notation
string strName;
int iCount;
bool bIsActive;
// GOOD: Clean names
string name;
int count;
bool isActive;
● Don't use abbreviations (except common ones)
// BAD: Unclear abbreviations
public class CustSvc { }
public int GetCnt() { }
string addr;
// GOOD: Full words or common abbreviations
public class CustomerService { }
public int GetCount() { }
string address;
// ACCEPTABLE: Common abbreviations
int customerId; // Id is acceptable
string imageUrl; // Url is acceptable
string htmlContent; // Html is acceptable
● Don't use single-letter names (except loop counters)
// BAD
Customer c;
Order o;
decimal t;
// GOOD
Customer customer;
Order order;
decimal total;
// ACCEPTABLE: Loop counters
for (int i = 0; i < [Link]; i++) { }
● Don't use underscores in public identifiers
// BAD
public string First_Name { get; set; }
public void Process_Order() { }
// GOOD
public string FirstName { get; set; }
public void ProcessOrder() { }
2.3 Real-World Examples
Example: Customer Service Class
// GOOD: Following all naming conventions
public class CustomerService : ICustomerService
{
private readonly ICustomerRepository _customerRepository;
private readonly ILogger<CustomerService> _logger;
private static readonly TimeSpan s_cacheExpiration = [Link](30);
public CustomerService(
ICustomerRepository customerRepository,
ILogger<CustomerService> logger)
{
_customerRepository = customerRepository;
_logger = logger;
}
public async Task<Customer?> GetCustomerByIdAsync(int customerId, CancellationToken
cancellationToken = default)
{
_logger.LogInformation("Fetching customer with ID: {CustomerId}", customerId);
var customer = await _customerRepository.GetByIdAsync(customerId,
cancellationToken);
return customer;
}
public async Task<IReadOnlyList<Customer>> GetActiveCustomersAsync(CancellationToken
cancellationToken = default)
{
var activeCustomers = await _customerRepository
.GetAllAsync(cancellationToken);
return activeCustomers
.Where(c => [Link])
.ToList();
}
}
Chapter 3: C# Coding Conventions
3.1 Code Formatting Rules
Indentation and Spacing
Use 4 spaces for indentation, never tabs. This ensures consistent formatting across different editors and
environments.
// GOOD: 4-space indentation
public class OrderService
{
public void ProcessOrder(Order order)
{
if ([Link])
{
foreach (var item in [Link])
{
ProcessItem(item);
}
}
}
}
Brace Style (Allman Style)
Always place opening braces on a new line:
// GOOD: Allman style (C# standard)
public class Customer
{
public void UpdateEmail(string email)
{
if ([Link](email))
{
throw new ArgumentException("Email cannot be empty");
}
Email = email;
}
}
// BAD: K&R style (not C# standard)
public class Customer {
public void UpdateEmail(string email) {
if ([Link](email)) {
throw new ArgumentException("Email cannot be empty");
}
Email = email;
}
}
Line Length
Keep lines under 120 characters, preferring 80-100 for better readability:
// BAD: Too long
var customers = await _context.[Link](c => [Link] && [Link] >
[Link](-30) && [Link]()).ToListAsync(cancellationToken);
// GOOD: Broken into readable lines
var customers = await _context.Customers
.Where(c => [Link])
.Where(c => [Link] > [Link](-30))
.Where(c => [Link]())
.ToListAsync(cancellationToken);
Blank Lines
Use blank lines to separate logical sections:
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository orderRepository, ILogger<OrderService> logger)
{
_orderRepository = orderRepository;
_logger = logger;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Validation
ValidateRequest(request);
// Create order
var order = new Order
{
CustomerId = [Link],
Items = [Link],
CreatedAt = [Link]
};
// Save and return
await _orderRepository.AddAsync(order);
return order;
}
private void ValidateRequest(CreateOrderRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
}
}
3.2 Using var Correctly
When to Use var ✅
Use var when the type is obvious from the right-hand side:
// GOOD: Type is obvious
var customer = new Customer();
var orders = new List<Order>();
var name = "John Doe";
var count = 42;
var dictionary = new Dictionary<string, int>();
// GOOD: Factory methods with clear return type
var stream = [Link]("[Link]");
var builder = [Link](args);
When to Avoid var ❌
Avoid var when the type is not immediately clear:
// BAD: What type is returned?
var result = ProcessOrder(order);
var data = GetData();
var value = Calculate(x, y);
// GOOD: Explicit type makes code clearer
OrderResult result = ProcessOrder(order);
CustomerData data = GetData();
decimal value = Calculate(x, y);
// GOOD: Explicit type in foreach for clarity
foreach (Order order in orders) // Not: foreach (var order in orders)
{
ProcessOrder(order);
}
3.3 String Handling Best Practices
String Interpolation (Preferred)
// GOOD: String interpolation
string message = $"Hello, {[Link]}! Your order #{[Link]} is ready.";
// BAD: String concatenation
string message = "Hello, " + [Link] + "! Your order #" + [Link] + " is ready.";
// BAD: [Link] (less readable)
string message = [Link]("Hello, {0}! Your order #{1} is ready.", [Link],
[Link]);
StringBuilder for Loops
// BAD: String concatenation in loop (creates many string objects)
string result = "";
foreach (var item in items)
{
result += [Link] + ", ";
}
// GOOD: StringBuilder for loops
var builder = new StringBuilder();
foreach (var item in items)
{
[Link]([Link]);
[Link](", ");
}
string result = [Link]();
// BETTER: [Link] for simple cases
string result = [Link](", ", [Link](i => [Link]));
Raw String Literals (C# 11+)
// GOOD: Raw string literals for JSON, SQL, etc.
string json = """
{
"name": "John",
"email": "john@[Link]",
"age": 30
}
""";
// GOOD: With interpolation
string json = $$"""
{
"name": "{{[Link]}}",
"email": "{{[Link]}}"
}
""";
Verbatim Strings for Paths
// GOOD: Verbatim string for file paths
string path = @"C:\Users\Documents\[Link]";
// BAD: Escaped backslashes
string path = "C:\\Users\\Documents\\[Link]";
3.4 Modern C# Features
Records (C# 9+)
Records are ideal for immutable data transfer objects:
// Simple record with positional parameters
public record CustomerDto(int Id, string Name, string Email);
// Usage
var customer = new CustomerDto(1, "John", "john@[Link]");
var updated = customer with { Email = "newemail@[Link]" }; // Non-destructive mutation
// Record with additional members
public record OrderDto(int Id, decimal Total)
{
public string FormattedTotal => $"${Total:F2}";
}
Primary Constructors (C# 12)
Simplify dependency injection:
// OLD: Verbose constructor
public class CustomerService
{
private readonly ICustomerRepository _repository;
private readonly ILogger<CustomerService> _logger;
public CustomerService(ICustomerRepository repository, ILogger<CustomerService> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<Customer?> GetAsync(int id) => await _repository.GetByIdAsync(id);
}
// NEW: Primary constructor (C# 12)
public class CustomerService(ICustomerRepository repository, ILogger<CustomerService>
logger)
{
public async Task<Customer?> GetAsync(int id) => await [Link](id);
}
Collection Expressions (C# 12)
// OLD
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
List<string> names = new List<string> { "Alice", "Bob" };
// NEW: Collection expressions
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob"];
// Works with spread operator
int[] moreNumbers = [0, ..numbers, 6, 7];
Required Members (C# 11)
public class Customer
{
public required string Name { get; init; }
public required string Email { get; init; }
public string? Phone { get; init; }
}
// Must provide required properties
var customer = new Customer
{
Name = "John", // Required
Email = "j@[Link]" // Required
// Phone is optional
};
3.5 LINQ Best Practices
Use Meaningful Lambda Parameter Names
// BAD: Single letter, unclear
var adults = [Link](x => [Link] >= 18);
// GOOD: Meaningful name
var adults = [Link](customer => [Link] >= 18);
// ACCEPTABLE: Short forms for simple operations
var names = [Link](c => [Link]);
Filter Early
// BAD: Processing all items, then filtering
var result = orders
.Select(o => new OrderDto([Link], [Link], CalculateExpensiveValue(o)))
.Where(dto => [Link] > 100)
.ToList();
// GOOD: Filter first, then transform
var result = orders
.Where(o => [Link] > 100) // Filter first
.Select(o => new OrderDto([Link], [Link], CalculateExpensiveValue(o)))
.ToList();
Materialize Once
// BAD: Multiple enumerations
IEnumerable<Customer> customers = GetCustomers();
[Link]($"Count: {[Link]()}"); // First enumeration
foreach (var customer in customers) { } // Second enumeration
// GOOD: Materialize to list once
List<Customer> customers = GetCustomers().ToList();
[Link]($"Count: {[Link]}"); // No enumeration
foreach (var customer in customers) { } // Single enumeration
3.6 Comments and Documentation
XML Documentation for Public APIs
/// <summary>
/// Retrieves a customer by their unique identifier.
/// </summary>
/// <param name="customerId">The unique identifier of the customer.</param>
/// <param name="cancellationToken">Token to cancel the operation.</param>
/// <returns>The customer if found; otherwise, null.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when customerId is less than or
equal to zero.</exception>
public async Task<Customer?> GetCustomerByIdAsync(int customerId, CancellationToken
cancellationToken = default)
{
if (customerId <= 0)
throw new ArgumentOutOfRangeException(nameof(customerId));
return await _repository.GetByIdAsync(customerId, cancellationToken);
}
When to Comment
// GOOD: Explain WHY, not WHAT
// Using retry logic because the payment gateway occasionally times out
await Policy
.Handle<TimeoutException>()
.RetryAsync(3)
.ExecuteAsync(() => ProcessPaymentAsync(order));
// BAD: Obvious comment
// Increment counter by 1
counter++;
// BAD: Comment instead of clear code
// Check if customer is valid
if (c != null && c.A && c.E != null) // What is A? E?
// GOOD: Self-documenting code
if (customer != null && [Link] && [Link] != null)
Chapter 4: Type Selection Guide
4.1 When to Use Record
Records are ideal for immutable data objects where value-based equality is important.
Best Use Cases for Records
● Data Transfer Objects (DTOs)
// API Request/Response models
public record CreateCustomerRequest(string Name, string Email, string Phone);
public record CustomerResponse(int Id, string Name, string Email, DateTime CreatedAt);
// Usage
var request = new CreateCustomerRequest("John", "john@[Link]", "123-456-7890");
● Value Objects in Domain-Driven Design
public record Address(string Street, string City, string State, string ZipCode)
{
public string FullAddress => $"{Street}, {City}, {State} {ZipCode}";
}
public record Money(decimal Amount, string Currency)
{
public static Money operator +(Money a, Money b)
{
if ([Link] != [Link])
throw new InvalidOperationException("Cannot add different currencies");
return new Money([Link] + [Link], [Link]);
}
}
● Immutable Configuration
public record DatabaseSettings(string ConnectionString, int MaxRetryCount, TimeSpan
Timeout);
Record Features
public record Person(string FirstName, string LastName)
{
public string FullName => $"{FirstName} {LastName}";
}
// Built-in value equality
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
[Link](person1 == person2); // True (value equality)
// Non-destructive mutation with 'with'
var person3 = person1 with { LastName = "Smith" };
// person3 is new Person("John", "Smith")
// Built-in deconstruction
var (firstName, lastName) = person1;
// Built-in ToString()
[Link](person1); // Person { FirstName = John, LastName = Doe }
4.2 When to Use Class
Classes are ideal for entities with behavior, identity, and mutable state.
Best Use Cases for Classes
● Domain Entities
public class Order
{
public int Id { get; private set; }
public int CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public List<OrderItem> Items { get; private set; } = new();
public DateTime CreatedAt { get; private set; }
public Order(int customerId)
{
CustomerId = customerId;
Status = [Link];
CreatedAt = [Link];
}
public void AddItem(int productId, int quantity, decimal price)
{
if (Status != [Link])
throw new InvalidOperationException("Cannot modify non-pending order");
[Link](new OrderItem(productId, quantity, price));
}
public void Submit()
{
if (![Link]())
throw new InvalidOperationException("Cannot submit empty order");
Status = [Link];
}
public void Cancel()
{
if (Status == [Link])
throw new InvalidOperationException("Cannot cancel shipped order");
Status = [Link];
}
public decimal Total => [Link](i => [Link] * [Link]);
}
● Services
public class EmailService : IEmailService
{
private readonly ISmtpClient _smtpClient;
private readonly ILogger<EmailService> _logger;
public EmailService(ISmtpClient smtpClient, ILogger<EmailService> logger)
{
_smtpClient = smtpClient;
_logger = logger;
}
public async Task SendAsync(string to, string subject, string body)
{
_logger.LogInformation("Sending email to {Recipient}", to);
await _smtpClient.SendAsync(to, subject, body);
}
}
● Base Classes for Inheritance
public abstract class Entity
{
public int Id { get; protected set; }
public DateTime CreatedAt { get; protected set; }
public DateTime? UpdatedAt { get; protected set; }
}
public class Customer : Entity
{
public string Name { get; private set; }
public string Email { get; private set; }
// ...
}
4.3 When to Use Struct
Structs are ideal for small, lightweight value types that are short-lived.
Best Use Cases for Structs
● Small Value Types (< 16 bytes)
public readonly struct Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y)
{
X = x;
Y = y;
}
public double DistanceTo(Point other)
{
var dx = X - other.X;
var dy = Y - other.Y;
return [Link](dx * dx + dy * dy);
}
}
● Performance-Critical Scenarios
public readonly struct DateRange
{
public DateTime Start { get; }
public DateTime End { get; }
public DateRange(DateTime start, DateTime end)
{
if (end < start)
throw new ArgumentException("End must be after start");
Start = start;
End = end;
}
public bool Contains(DateTime date) => date >= Start && date <= End;
public TimeSpan Duration => End - Start;
}
Struct Guidelines
● Keep structs small (< 16 bytes)
● Make them immutable (readonly struct)
● Use for short-lived values
● Don't use for types that need inheritance
4.4 Decision Flowchart
┌─────────────────────┐
│ Need a new type? │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Is it a DTO/Data │
┌─────│ carrier only? │─────┐
│ └─────────────────────┘ │
│ Yes │ No
▼ ▼
┌─────────┐ ┌───────────────┐
│ RECORD │ │ Has behavior/ │
└─────────┘ ┌─────│ mutations? │─────┐
│ └───────────────┘ │
│ Yes │ No
▼ ▼
┌─────────┐ ┌───────────────┐
│ CLASS │ │ Small (<16B) │
└─────────┘ ┌─────│ & short-lived?│─────┐
│ └───────────────┘ │
│ Yes │ No
▼ ▼
┌─────────┐ ┌─────────┐
│ STRUCT │ │ CLASS │
└─────────┘ └─────────┘
Chapter 5: Nullable Reference Types
5.1 Enabling NRT
Nullable Reference Types (NRT) help prevent null reference exceptions at compile time. Enable them project-
wide:
<PropertyGroup>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>
**Best Practice:** Treat nullable warnings as errors to enforce null safety throughout your codebase.
5.2 Understanding Nullable Annotations
Non-Nullable (Default)
public class Customer
{
// These properties can NEVER be null
public string Name { get; set; } // Must have a value
public string Email { get; set; } // Must have a value
// Constructor ensures non-null
public Customer(string name, string email)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Email = email ?? throw new ArgumentNullException(nameof(email));
}
}
Nullable
public class Customer
{
public string Name { get; set; }
public string? MiddleName { get; set; } // Can be null
public string? Phone { get; set; } // Can be null
}
// Method that may return null
public Customer? FindCustomerById(int id)
{
return _customers.FirstOrDefault(c => [Link] == id);
}
5.3 Null Handling Patterns
Null-Conditional Operator (?.)
// Instead of
string? city = null;
if (customer != null && [Link] != null)
{
city = [Link];
}
// Use null-conditional
string? city = customer?.Address?.City;
// With method calls
int? length = customer?.Name?.Length;
customer?.Orders?.Clear();
Null-Coalescing Operator (??)
// Provide default value
string name = customer?.Name ?? "Unknown";
int count = items?.Count ?? 0;
// Chain multiple fallbacks
string displayName = customer?.DisplayName ?? customer?.Name ?? "Guest";
Null-Coalescing Assignment (??=)
// Initialize if null
_cache ??= new Dictionary<string, object>();
// Equivalent to
if (_cache == null)
{
_cache = new Dictionary<string, object>();
}
Null-Forgiving Operator (!)
Use sparingly when you're certain a value isn't null:
// Only when you're 100% sure
public void ProcessOrder(Order order)
{
// We know Customer is loaded due to Include()
string customerName = [Link]!.Name;
}
// Better: Use proper null checks
public void ProcessOrder(Order order)
{
if ([Link] is null)
throw new InvalidOperationException("Customer must be loaded");
string customerName = [Link];
}
Pattern Matching with Null Checks
// Check for not null with pattern
if (customer is not null)
{
[Link]([Link]);
}
// Check for null
if (customer is null)
{
return NotFound();
}
// Combine with property patterns
if (customer is { Name: not null, Email: not null } validCustomer)
{
SendWelcomeEmail(validCustomer);
}
// Switch expression with null
string status = order?.Status switch
{
[Link] => "Waiting",
[Link] => "On the way",
null => "Unknown",
_ => "Other"
};
5.4 Common Pitfalls
Pitfall 1: Forgetting Async Null Returns
// BAD: May return null task
public Task<Customer>? GetCustomerAsync(int id) // Nullable Task!
{
if (id <= 0) return null;
return _repository.GetByIdAsync(id);
}
// GOOD: Return Task with nullable result
public async Task<Customer?> GetCustomerAsync(int id)
{
if (id <= 0) return null;
return await _repository.GetByIdAsync(id);
}
Pitfall 2: Generic Type Constraints
// May cause issues with nullable
public T GetOrDefault<T>(string key)
{
return _cache.TryGetValue(key, out var value) ? (T)value : default!;
}
// Better: Be explicit about nullability
public T? GetOrDefault<T>(string key) where T : class
{
return _cache.TryGetValue(key, out var value) ? (T)value : null;
}
Chapter 6: SOLID Principles
6.1 Single Responsibility Principle (SRP)
**"A class should have only one reason to change."**
Bad Example ❌
// BAD: This class has multiple responsibilities
public class CustomerService
{
public Customer CreateCustomer(string name, string email)
{
// 1. Validation logic
if ([Link](name))
throw new ArgumentException("Name required");
if ()
throw new ArgumentException("Invalid email");
// 2. Database logic
var customer = new Customer { Name = name, Email = email };
using var connection = new SqlConnection(_connectionString);
[Link]("INSERT INTO Customers...", customer);
// 3. Email logic
var smtpClient = new SmtpClient("[Link]");
[Link]("welcome@[Link]", email, "Welcome!", "...");
// 4. Logging logic
[Link]("[Link]", $"Customer created: {name}");
return customer;
}
}
Good Example ✅
// GOOD: Each class has a single responsibility
// Responsibility: Validation
public class CustomerValidator : ICustomerValidator
{
public ValidationResult Validate(CreateCustomerRequest request)
{
var errors = new List<string>();
if ([Link]([Link]))
[Link]("Name is required");
if (!IsValidEmail([Link]))
[Link]("Invalid email format");
return new ValidationResult(errors);
}
}
// Responsibility: Data persistence
public class CustomerRepository : ICustomerRepository
{
private readonly AppDbContext _context;
public async Task<Customer> AddAsync(Customer customer)
{
_context.[Link](customer);
await _context.SaveChangesAsync();
return customer;
}
}
// Responsibility: Email sending
public class EmailService : IEmailService
{
private readonly ISmtpClient _smtpClient;
public async Task SendWelcomeEmailAsync(string to, string customerName)
{
await _smtpClient.SendAsync(to, "Welcome!", $"Hello {customerName}...");
}
}
// Responsibility: Orchestration
public class CustomerService : ICustomerService
{
private readonly ICustomerValidator _validator;
private readonly ICustomerRepository _repository;
private readonly IEmailService _emailService;
private readonly ILogger<CustomerService> _logger;
public CustomerService(
ICustomerValidator validator,
ICustomerRepository repository,
IEmailService emailService,
ILogger<CustomerService> logger)
{
_validator = validator;
_repository = repository;
_emailService = emailService;
_logger = logger;
}
public async Task<Customer> CreateCustomerAsync(CreateCustomerRequest request)
{
var validation = _validator.Validate(request);
if (![Link])
throw new ValidationException([Link]);
var customer = new Customer([Link], [Link]);
await _repository.AddAsync(customer);
await _emailService.SendWelcomeEmailAsync([Link], [Link]);
_logger.LogInformation("Customer created: {CustomerId}", [Link]);
return customer;
}
}
6.2 Open/Closed Principle (OCP)
**"Software entities should be open for extension but closed for modification."**
Bad Example ❌
// BAD: Must modify class to add new discount types
public class DiscountCalculator
{
public decimal CalculateDiscount(Order order, string discountType)
{
switch (discountType)
{
case "Percentage":
return [Link] * 0.10m;
case "FixedAmount":
return 50m;
case "BuyOneGetOne":
return [Link] * 0.50m;
// Must add new cases here for every new discount type
default:
return 0m;
}
}
}
Good Example ✅
// GOOD: Open for extension via new implementations
public interface IDiscountStrategy
{
decimal CalculateDiscount(Order order);
}
public class PercentageDiscount : IDiscountStrategy
{
private readonly decimal _percentage;
public PercentageDiscount(decimal percentage) => _percentage = percentage;
public decimal CalculateDiscount(Order order) => [Link] * _percentage;
}
public class FixedAmountDiscount : IDiscountStrategy
{
private readonly decimal _amount;
public FixedAmountDiscount(decimal amount) => _amount = amount;
public decimal CalculateDiscount(Order order) => [Link](_amount, [Link]);
}
public class LoyaltyDiscount : IDiscountStrategy
{
public decimal CalculateDiscount(Order order)
{
return [Link] switch
{
>= 5 => [Link] * 0.15m,
>= 2 => [Link] * 0.10m,
>= 1 => [Link] * 0.05m,
_ => 0m
};
}
}
// Adding new discount types doesn't require modifying existing code
public class HolidayDiscount : IDiscountStrategy
{
public decimal CalculateDiscount(Order order)
{
return [Link] == 12 ? [Link] * 0.20m : 0m;
}
}
// Calculator is closed for modification
public class DiscountCalculator
{
public decimal CalculateDiscount(Order order, IDiscountStrategy strategy)
{
return [Link](order);
}
}
6.3 Liskov Substitution Principle (LSP)
**"Subtypes must be substitutable for their base types."**
Bad Example ❌
// BAD: Square violates LSP when substituted for Rectangle
public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
public int Area => Width * Height;
}
public class Square : Rectangle
{
public override int Width
{
get => [Link];
set { [Link] = value; [Link] = value; }
}
public override int Height
{
get => [Link];
set { [Link] = value; [Link] = value; }
}
}
// This breaks when using Square!
public void ResizeRectangle(Rectangle rect)
{
[Link] = 10;
[Link] = 5;
// Expected area: 50
// With Square: Area is 25 (5x5) - UNEXPECTED!
}
Good Example ✅
// GOOD: Use proper abstraction
public interface IShape
{
int Area { get; }
}
public class Rectangle : IShape
{
public int Width { get; }
public int Height { get; }
public int Area => Width * Height;
public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
}
public class Square : IShape
{
public int Side { get; }
public int Area => Side * Side;
public Square(int side)
{
Side = side;
}
}
// Both can be used interchangeably through IShape
public void PrintArea(IShape shape)
{
[Link]($"Area: {[Link]}");
}
6.4 Interface Segregation Principle (ISP)
**"Clients should not be forced to depend on interfaces they do not use."**
Bad Example ❌
// BAD: Fat interface forces unnecessary implementations
public interface IRepository<T>
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
Task<int> CountAsync();
Task<bool> ExistsAsync(int id);
Task BulkInsertAsync(IEnumerable<T> entities);
Task<IEnumerable<T>> SearchAsync(string query);
Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize);
}
// Read-only service forced to implement write methods
public class ReportRepository : IRepository<Report>
{
public Task AddAsync(Report entity) => throw new NotSupportedException();
public Task UpdateAsync(Report entity) => throw new NotSupportedException();
public Task DeleteAsync(int id) => throw new NotSupportedException();
// ... other methods
}
Good Example ✅
// GOOD: Segregated interfaces
public interface IReadRepository<T>
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<bool> ExistsAsync(int id);
}
public interface IWriteRepository<T>
{
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
public interface IPagedRepository<T>
{
Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize);
Task<int> CountAsync();
}
public interface ISearchableRepository<T>
{
Task<IEnumerable<T>> SearchAsync(string query);
}
// Full CRUD repository combines what it needs
public interface ICustomerRepository :
IReadRepository<Customer>,
IWriteRepository<Customer>,
IPagedRepository<Customer>
{ }
// Read-only repository only implements reading
public interface IReportRepository :
IReadRepository<Report>,
ISearchableRepository<Report>
{ }
6.5 Dependency Inversion Principle (DIP)
**"High-level modules should not depend on low-level modules. Both should depend on
abstractions."**
Bad Example ❌
// BAD: High-level module depends on low-level implementation
public class OrderService
{
private readonly SqlOrderRepository _repository; // Concrete dependency
private readonly SmtpEmailSender _emailSender; // Concrete dependency
public OrderService()
{
_repository = new SqlOrderRepository(); // Hardcoded
_emailSender = new SmtpEmailSender(); // Hardcoded
}
public void CreateOrder(Order order)
{
_repository.Save(order);
_emailSender.Send([Link], "Order Confirmation", "...");
}
}
Good Example ✅
// GOOD: Depend on abstractions, inject dependencies
public interface IOrderRepository
{
Task SaveAsync(Order order);
}
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body);
}
public class OrderService : IOrderService
{
private readonly IOrderRepository _repository;
private readonly IEmailSender _emailSender;
// Dependencies injected through constructor
public OrderService(IOrderRepository repository, IEmailSender emailSender)
{
_repository = repository;
_emailSender = emailSender;
}
public async Task CreateOrderAsync(Order order)
{
await _repository.SaveAsync(order);
await _emailSender.SendAsync(
[Link],
"Order Confirmation",
$"Your order #{[Link]} has been placed.");
}
}
// Registration in DI container
[Link]<IOrderRepository, SqlOrderRepository>();
[Link]<IEmailSender, SmtpEmailSender>();
[Link]<IOrderService, OrderService>();
Chapter 7: Clean Architecture
7.1 Project Structure Overview
Clean Architecture organizes code into layers with dependencies pointing inward:
┌─────────────────────────────────────────────────────────────┐
│ WebAPI │
│ (Controllers, Middleware, Filters) │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure │
│ (DbContext, Repositories, External Services) │
├─────────────────────────────────────────────────────────────┤
│ Application │
│ (Services, DTOs, Validators, MediatR Handlers) │
├─────────────────────────────────────────────────────────────┤
│ Domain │
│ (Entities, Value Objects, Interfaces) │
└─────────────────────────────────────────────────────────────┘
Dependency Direction: Outer layers depend on inner layers
Inner layers know nothing about outer layers
7.2 Domain Layer
The innermost layer containing business logic and entities:
/src/Domain/
├── Entities/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── ValueObjects/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── Enums/
│ ├── [Link]
│ └── [Link]
├── Exceptions/
│ ├── [Link]
│ └── [Link]
├── Interfaces/
│ ├── [Link]
│ └── [Link]
└── Services/
└── [Link]
Domain Entity Example
namespace [Link];
public class Order
{
public int Id { get; private set; }
public int CustomerId { get; private set; }
public Customer Customer { get; private set; } = null!;
public OrderStatus Status { get; private set; }
public Money Total { get; private set; }
public DateTime CreatedAt { get; private set; }
private readonly List<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
private Order() { } // EF Core
public Order(int customerId)
{
CustomerId = customerId;
Status = [Link];
Total = [Link]("USD");
CreatedAt = [Link];
}
public void AddItem(Product product, int quantity)
{
if (Status != [Link])
throw new DomainException("Cannot modify non-pending order");
var existingItem = _items.FirstOrDefault(i => [Link] == [Link]);
if (existingItem != null)
{
[Link](quantity);
}
else
{
_items.Add(new OrderItem(product, quantity));
}
RecalculateTotal();
}
public void Submit()
{
if (!_items.Any())
throw new DomainException("Cannot submit empty order");
Status = [Link];
}
private void RecalculateTotal()
{
Total = _items.Aggregate(
[Link]("USD"),
(sum, item) => sum + [Link]);
}
}
7.3 Application Layer
Contains application logic, DTOs, and orchestration:
/src/Application/
├── Common/
│ ├── Interfaces/
│ │ ├── [Link]
│ │ └── [Link]
│ ├── Behaviors/
│ │ ├── [Link]
│ │ └── [Link]
│ └── Exceptions/
│ └── [Link]
├── Customers/
│ ├── Commands/
│ │ ├── CreateCustomer/
│ │ │ ├── [Link]
│ │ │ ├── [Link]
│ │ │ └── [Link]
│ │ └── UpdateCustomer/
│ │ └── ...
│ ├── Queries/
│ │ ├── GetCustomerById/
│ │ │ ├── [Link]
│ │ │ ├── [Link]
│ │ │ └── [Link]
│ │ └── GetCustomersList/
│ │ └── ...
│ └── EventHandlers/
│ └── [Link]
└── [Link]
CQRS Example with MediatR
// Command
public record CreateCustomerCommand(string Name, string Email) : IRequest<int>;
// Handler
public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, int>
{
private readonly IApplicationDbContext _context;
public CreateCustomerCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<int> Handle(CreateCustomerCommand request, CancellationToken
cancellationToken)
{
var customer = new Customer([Link], [Link]);
_context.[Link](customer);
await _context.SaveChangesAsync(cancellationToken);
return [Link];
}
}
// Validator
public class CreateCustomerCommandValidator : AbstractValidator<CreateCustomerCommand>
{
public CreateCustomerCommandValidator()
{
RuleFor(x => [Link])
.NotEmpty()
.MaximumLength(100);
RuleFor(x => [Link])
.NotEmpty()
.EmailAddress();
}
}
7.4 Infrastructure Layer
Implements interfaces defined in Domain/Application:
/src/Infrastructure/
├── Data/
│ ├── [Link]
│ ├── Configurations/
│ │ ├── [Link]
│ │ └── [Link]
│ └── Migrations/
├── Repositories/
│ ├── [Link]
│ └── [Link]
├── Services/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
└── [Link]
Repository Implementation
namespace [Link];
public class CustomerRepository : ICustomerRepository
{
private readonly ApplicationDbContext _context;
public CustomerRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Customer?> GetByIdAsync(int id, CancellationToken cancellationToken =
default)
{
return await _context.Customers
.Include(c => [Link])
.FirstOrDefaultAsync(c => [Link] == id, cancellationToken);
}
public async Task<IEnumerable<Customer>> GetAllAsync(CancellationToken
cancellationToken = default)
{
return await _context.Customers
.AsNoTracking()
.ToListAsync(cancellationToken);
}
public async Task AddAsync(Customer customer, CancellationToken cancellationToken =
default)
{
await _context.[Link](customer, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
}
7.5 Presentation Layer (WebAPI)
/src/WebAPI/
├── Controllers/
│ ├── [Link]
│ └── [Link]
├── Middleware/
│ ├── [Link]
│ └── [Link]
├── Filters/
│ └── [Link]
├── Extensions/
│ └── [Link]
└── [Link]
Controller Example
namespace [Link];
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private readonly ISender _mediator;
public CustomersController(ISender mediator)
{
_mediator = mediator;
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(CustomerDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CustomerDto>> GetById(int id)
{
var customer = await _mediator.Send(new GetCustomerByIdQuery(id));
if (customer is null)
return NotFound();
return Ok(customer);
}
[HttpPost]
[ProducesResponseType(typeof(int), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<int>> Create(CreateCustomerCommand command)
{
var customerId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = customerId }, customerId);
}
}
Chapter 8: [Link] Core Fundamentals
8.1 Async Programming Rules
Rule 1: Async All The Way
// BAD: Blocking async code
public Customer GetCustomer(int id)
{
// NEVER DO THIS - causes deadlocks and thread pool starvation
return _repository.GetByIdAsync(id).Result;
}
public void ProcessOrder(Order order)
{
// NEVER DO THIS
_emailService.SendAsync([Link], "...", "...").Wait();
}
// GOOD: Async all the way
public async Task<Customer> GetCustomerAsync(int id)
{
return await _repository.GetByIdAsync(id);
}
public async Task ProcessOrderAsync(Order order)
{
await _emailService.SendAsync([Link], "...", "...");
}
Rule 2: Always Use CancellationToken
// GOOD: Accept and pass CancellationToken
public async Task<IEnumerable<Customer>> GetAllCustomersAsync(CancellationToken
cancellationToken = default)
{
return await _context.Customers
.AsNoTracking()
.ToListAsync(cancellationToken);
}
// Controller automatically provides cancellation token
[HttpGet]
public async Task<ActionResult<IEnumerable<CustomerDto>>> GetAll(CancellationToken
cancellationToken)
{
var customers = await _customerService.GetAllAsync(cancellationToken);
return Ok(customers);
}
Rule 3: Never Use async void
// BAD: async void - exceptions are unobservable
public async void ProcessOrderBad(Order order)
{
await _repository.SaveAsync(order); // If this throws, exception is lost!
}
// GOOD: async Task
public async Task ProcessOrderAsync(Order order)
{
await _repository.SaveAsync(order);
}
// EXCEPTION: Event handlers can use async void
[Link] += async (sender, e) =>
{
await ProcessAsync();
};
8.2 HttpContext Best Practices
Never Store HttpContext in a Field
// BAD: Storing HttpContext
public class BadService
{
private readonly HttpContext _httpContext; // DON'T DO THIS
public BadService(IHttpContextAccessor accessor)
{
_httpContext = [Link]!; // Captured at construction time
}
}
// GOOD: Access when needed
public class GoodService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public GoodService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string? GetCurrentUserId()
{
// Access HttpContext when needed
return
_httpContextAccessor.HttpContext?.User?.FindFirst([Link])?.Value;
}
}
Copy Values for Background Work
// BAD: Using HttpContext in background task
public async Task ProcessInBackground()
{
_ = [Link](async () =>
{
// HttpContext may be null or disposed here!
var userId = _httpContextAccessor.HttpContext?.User?.Identity?.Name;
await DoWorkAsync(userId);
});
}
// GOOD: Copy values before background work
public async Task ProcessInBackground()
{
// Copy values while HttpContext is still available
var userId = _httpContextAccessor.HttpContext?.User?.Identity?.Name;
var correlationId = _httpContextAccessor.HttpContext?.TraceIdentifier;
_ = [Link](async () =>
{
// Use copied values
await DoWorkAsync(userId, correlationId);
});
}
8.3 HttpClient Factory Pattern
Why Not Create HttpClient Directly?
// BAD: Creates socket exhaustion
public class BadService
{
public async Task<string> GetDataAsync(string url)
{
// Each call creates new HttpClient - BAD!
using var client = new HttpClient();
return await [Link](url);
}
}
// Also BAD: Single shared instance doesn't respect DNS changes
public class AlsoBadService
{
private static readonly HttpClient _client = new HttpClient();
}
Using IHttpClientFactory
// Registration
[Link]();
// Basic usage
public class MyService
{
private readonly IHttpClientFactory _httpClientFactory;
public MyService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<string> GetDataAsync(string url)
{
var client = _httpClientFactory.CreateClient();
return await [Link](url);
}
}
Named Clients
// Registration with configuration
[Link]("GitHub", client =>
{
[Link] = new Uri("[Link]
[Link]("Accept", "application/[Link].v3+json");
[Link]("User-Agent", "MyApp");
});
// Usage
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public async Task<string> GetUserAsync(string username)
{
var client = _httpClientFactory.CreateClient("GitHub");
return await [Link]($"users/{username}");
}
}
Typed Clients (Recommended)
// Typed client
public class GitHubClient
{
private readonly HttpClient _httpClient;
public GitHubClient(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("[Link]
_httpClient.[Link]("Accept", "application/[Link].v3+json");
}
public async Task<GitHubUser?> GetUserAsync(string username)
{
return await _httpClient.GetFromJsonAsync<GitHubUser>($"users/{username}");
}
}
// Registration
[Link]<GitHubClient>();
// Usage - inject directly
public class MyService
{
private readonly GitHubClient _gitHubClient;
public MyService(GitHubClient gitHubClient)
{
_gitHubClient = gitHubClient;
}
}
8.4 Background Services
public class OrderProcessingService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<OrderProcessingService> _logger;
public OrderProcessingService(
IServiceScopeFactory scopeFactory,
ILogger<OrderProcessingService> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (![Link])
{
try
{
// Create scope for scoped services (like DbContext)
await using var scope = _scopeFactory.CreateAsyncScope();
var orderRepository =
[Link]<IOrderRepository>();
var emailService =
[Link]<IEmailService>();
var pendingOrders = await
[Link](stoppingToken);
foreach (var order in pendingOrders)
{
await ProcessOrderAsync(order, emailService, stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing orders");
}
await [Link]([Link](1), stoppingToken);
}
}
private async Task ProcessOrderAsync(Order order, IEmailService emailService,
CancellationToken ct)
{
// Process order logic
}
}
// Registration
[Link]<OrderProcessingService>();
Chapter 9: Middleware Pipeline
9.1 Understanding Middleware Order
Middleware order is critical for security and correct behavior. Each middleware can:
● Handle the request and short-circuit the pipeline
● Pass the request to the next middleware
● Execute code before and after the next middleware
Request → [Middleware 1] → [Middleware 2] → [Middleware 3] → Endpoint
↑ ↑ ↑
Response ← [Middleware 1] ← [Middleware 2] ← [Middleware 3] ←
9.2 Correct Pipeline Configuration
var builder = [Link](args);
// Add services
[Link]();
[Link]();
[Link]();
var app = [Link]();
// 1. Exception handling - MUST be first to catch all exceptions
[Link]("/error");
// 2. HSTS - Security header for HTTPS
if (![Link]())
{
[Link]();
}
// 3. HTTPS Redirection - Redirect HTTP to HTTPS
[Link]();
// 4. Static Files - Short-circuit for static content
[Link]();
// OR for .NET 9+:
// [Link]().ShortCircuit();
// 5. Swagger (development only, before routing)
if ([Link]())
{
[Link]();
[Link]();
}
// 6. Routing - Determines which endpoint to execute
[Link]();
// 7. CORS - Must be after routing, before auth
[Link]("AllowSpecificOrigins");
// 8. Authentication - Identifies the user
[Link]();
// 9. Authorization - Checks user permissions
[Link]();
// 10. Rate Limiting (.NET 7+)
[Link]();
// 11. Custom middleware (example)
[Link]();
// 12. Endpoints - Map controllers/minimal APIs
[Link]();
[Link]("/health");
[Link]();
Why Order Matters
Middleware Why This Position?
ExceptionHandler First - catches all exceptions from downstream
HSTS Early - security headers before any content
HttpsRedirection Early - redirect before processing
StaticFiles Early - short-circuit for static content
Routing Before auth - need to know endpoint for authorization
CORS After routing - needs endpoint info
Authentication After routing, before authorization
Authorization After authentication - needs identity
RateLimiter After auth - can apply per-user limits
9.3 Custom Middleware Creation
Convention-Based Middleware
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware>
logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Before the next middleware
var stopwatch = [Link]();
_logger.LogInformation(
"Request: {Method} {Path}",
[Link],
[Link]);
try
{
// Call next middleware
await _next(context);
}
finally
{
// After the next middleware
[Link]();
_logger.LogInformation(
"Response: {StatusCode} in {ElapsedMs}ms",
[Link],
[Link]);
}
}
}
// Extension method for clean registration
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return [Link]<RequestLoggingMiddleware>();
}
}
// Usage
[Link]();
Security Headers Middleware
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Add security headers before response is sent
[Link](() =>
{
var headers = [Link];
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "DENY";
headers["X-XSS-Protection"] = "0"; // Deprecated - disable
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
headers["Content-Security-Policy"] = "default-src 'self'";
return [Link];
});
await _next(context);
}
}
Chapter 10: API Design
10.1 RESTful API Conventions
URL Structure
GET /api/customers → Get all customers
GET /api/customers/{id} → Get customer by ID
POST /api/customers → Create new customer
PUT /api/customers/{id} → Update entire customer
PATCH /api/customers/{id} → Partial update
DELETE /api/customers/{id} → Delete customer
# Nested resources
GET /api/customers/{id}/orders → Get customer's orders
POST /api/customers/{id}/orders → Create order for customer
# Query parameters for filtering/sorting
GET /api/customers?status=active&sort=name&page=1&pageSize=10
Naming Rules
Rule Good ✅ Bad ❌
Use nouns, not verbs `/api/customers` `/api/getCustomers`
Use plural nouns `/api/customers` `/api/customer`
Use lowercase `/api/customers` `/api/Customers`
Use hyphens for readability `/api/order-items` `/api/orderItems`
10.2 HTTP Status Codes Guide
Success Codes (2xx)
Code Name When to Use
200 OK Successful GET, PUT, PATCH
201 Created Successful POST creating resource
204 No Content Successful DELETE, or PUT with no response body
Client Error Codes (4xx)
Code Name When to Use
400 Bad Request Invalid request syntax, validation errors
401 Unauthorized Missing or invalid authentication
403 Forbidden Authenticated but not authorized
404 Not Found Resource doesn't exist
409 Conflict Conflict with current state (duplicate, version)
422 Unprocessable Entity Validation errors on semantically correct request
Server Error Codes (5xx)
Code Name When to Use
500 Internal Server Error Unexpected server error
503 Service Unavailable Server temporarily unavailable
10.3 Request/Response Patterns
Success Response
// Single resource
[HttpGet("{id}")]
public async Task<ActionResult<CustomerDto>> GetById(int id)
{
var customer = await _service.GetByIdAsync(id);
if (customer is null)
return NotFound();
return Ok(customer); // 200 with customer data
}
// Create resource
[HttpPost]
public async Task<ActionResult<CustomerDto>> Create(CreateCustomerRequest request)
{
var customer = await _service.CreateAsync(request);
return CreatedAtAction(
nameof(GetById),
new { id = [Link] },
customer); // 201 with location header
}
Error Response (Problem Details - RFC 7807)
// Configure Problem Details
[Link]();
// Controller returning Problem Details
[HttpGet("{id}")]
public async Task<ActionResult<CustomerDto>> GetById(int id)
{
if (id <= 0)
{
return Problem(
title: "Invalid customer ID",
detail: "Customer ID must be greater than zero",
statusCode: StatusCodes.Status400BadRequest);
}
var customer = await _service.GetByIdAsync(id);
if (customer is null)
{
return Problem(
title: "Customer not found",
detail: $"Customer with ID {id} was not found",
statusCode: StatusCodes.Status404NotFound);
}
return Ok(customer);
}
Problem Details Response Format:
{
"type": "[Link]
"title": "Customer not found",
"status": 404,
"detail": "Customer with ID 123 was not found",
"traceId": "00-abc123-def456-00"
}
Pagination Response
public record PagedResult<T>(
IEnumerable<T> Items,
int Page,
int PageSize,
int TotalCount)
{
public int TotalPages => (int)[Link](TotalCount / (double)PageSize);
public bool HasPreviousPage => Page > 1;
public bool HasNextPage => Page < TotalPages;
}
[HttpGet]
public async Task<ActionResult<PagedResult<CustomerDto>>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
var result = await _service.GetPagedAsync(page, pageSize);
return Ok(result);
}
Pagination Response:
{
"items": [...],
"page": 1,
"pageSize": 10,
"totalCount": 100,
"totalPages": 10,
"hasPreviousPage": false,
"hasNextPage": true
}
10.4 Minimal APIs Example
var builder = [Link](args);
[Link]<ICustomerService, CustomerService>();
[Link]();
[Link]();
var app = [Link]();
// Group endpoints
var customers = [Link]("/api/customers")
.WithTags("Customers")
.RequireAuthorization();
[Link]("/", async (ICustomerService service, CancellationToken ct) =>
{
var customers = await [Link](ct);
return [Link](customers);
})
.WithName("GetAllCustomers")
.WithOpenApi();
[Link]("/{id:int}", async (int id, ICustomerService service, CancellationToken
ct) =>
{
var customer = await [Link](id, ct);
return customer is not null
? [Link](customer)
: [Link]();
})
.WithName("GetCustomerById")
.WithOpenApi();
[Link]("/", async (CreateCustomerRequest request, ICustomerService service,
CancellationToken ct) =>
{
var customer = await [Link](request, ct);
return [Link]($"/api/customers/{[Link]}", customer);
})
.WithName("CreateCustomer")
.WithOpenApi();
[Link]("/{id:int}", async (int id, ICustomerService service, CancellationToken
ct) =>
{
var deleted = await [Link](id, ct);
return deleted ? [Link]() : [Link]();
})
.WithName("DeleteCustomer")
.WithOpenApi();
[Link]();
Chapter 11: Async/Await Deep Dive
11.1 Understanding Async/Await
Async/await enables non-blocking I/O operations, allowing threads to be released back to the thread pool while
waiting for I/O to complete.
How It Works
public async Task<Customer> GetCustomerAsync(int id)
{
// 1. Thread starts executing
// 2. Hits await, releases thread back to pool
var customer = await _repository.GetByIdAsync(id);
// 3. When I/O completes, continues on available thread
return customer;
}
Thread Pool Efficiency
Synchronous (Blocking):
Thread 1: [====BLOCKED====] → Wasted
Thread 2: [====BLOCKED====] → Wasted
Thread 3: [====BLOCKED====] → Wasted
Asynchronous (Non-Blocking):
Thread 1: [Start]→[Free]→[Continue]→[Free]→[Continue]
↓ Request ↓ Request ↓ Request
All handled by same thread!
11.2 Common Async Patterns
Parallel Independent Operations
// BAD: Sequential execution
public async Task<DashboardData> GetDashboardDataBad()
{
var customers = await _customerService.GetAllAsync(); // Wait...
var orders = await _orderService.GetRecentAsync(); // Wait...
var revenue = await _reportService.GetMonthlyRevenueAsync(); // Wait...
// Total: 300ms + 400ms + 200ms = 900ms
return new DashboardData(customers, orders, revenue);
}
// GOOD: Parallel execution with [Link]
public async Task<DashboardData> GetDashboardDataGood()
{
var customersTask = _customerService.GetAllAsync();
var ordersTask = _orderService.GetRecentAsync();
var revenueTask = _reportService.GetMonthlyRevenueAsync();
await [Link](customersTask, ordersTask, revenueTask);
// Total: Max(300ms, 400ms, 200ms) = 400ms
return new DashboardData(
await customersTask,
await ordersTask,
await revenueTask);
}
Async Streaming (IAsyncEnumerable)
// Return items as they become available
public async IAsyncEnumerable<Customer> GetCustomersStreamAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var customer in
_context.[Link]().WithCancellation(cancellationToken))
{
yield return customer;
}
}
// Consume the stream
await foreach (var customer in GetCustomersStreamAsync(cancellationToken))
{
[Link]([Link]);
}
ValueTask for Hot Paths
// Use ValueTask when result is often cached/synchronous
public ValueTask<Customer?> GetCustomerAsync(int id)
{
// Check cache first (synchronous)
if (_cache.TryGetValue(id, out Customer? customer))
{
return [Link](customer); // No allocation
}
// Fall back to async database call
return new ValueTask<Customer?>(GetFromDatabaseAsync(id));
}
private async Task<Customer?> GetFromDatabaseAsync(int id)
{
var customer = await _repository.GetByIdAsync(id);
_cache[id] = customer;
return customer;
}
11.3 CancellationToken Best Practices
Always Accept and Pass CancellationToken
public class CustomerService : ICustomerService
{
private readonly ICustomerRepository _repository;
private readonly IEmailService _emailService;
public async Task<Customer> CreateCustomerAsync(
CreateCustomerRequest request,
CancellationToken cancellationToken = default) // Accept token
{
var customer = new Customer([Link], [Link]);
// Pass to all async operations
await _repository.AddAsync(customer, cancellationToken);
await _emailService.SendWelcomeEmailAsync([Link], cancellationToken);
return customer;
}
}
Checking for Cancellation
public async Task ProcessLargeDatasetAsync(
IEnumerable<Order> orders,
CancellationToken cancellationToken)
{
foreach (var order in orders)
{
// Check cancellation at each iteration
[Link]();
await ProcessOrderAsync(order, cancellationToken);
}
}
Linked CancellationTokens
public async Task<Customer> GetWithTimeoutAsync(int id, CancellationToken
cancellationToken)
{
// Create timeout token
using var cts = new CancellationTokenSource([Link](30));
// Link with caller's token
using var linked = [Link](
cancellationToken,
[Link]);
return await _repository.GetByIdAsync(id, [Link]);
}
11.4 Common Async Pitfalls
Pitfall 1: Async Void
// BAD: Exceptions are unobservable, cannot be awaited
public async void ProcessOrderBad(Order order)
{
await _repository.SaveAsync(order); // If throws, exception is LOST!
}
// GOOD: Use async Task
public async Task ProcessOrderAsync(Order order)
{
await _repository.SaveAsync(order); // Exceptions propagate correctly
}
Pitfall 2: Blocking on Async Code
// BAD: Can cause deadlocks and thread pool starvation
public Customer GetCustomerBad(int id)
{
return _repository.GetByIdAsync(id).Result; // NEVER DO THIS
}
public void ProcessOrderBad(Order order)
{
_emailService.SendAsync([Link], "...", "...").Wait(); // NEVER DO THIS
}
// GOOD: Async all the way
public async Task<Customer> GetCustomerAsync(int id)
{
return await _repository.GetByIdAsync(id);
}
Pitfall 3: Unnecessary Async State Machine
// UNNECESSARY: Adds overhead for simple delegation
public async Task<Customer> GetCustomerBad(int id)
{
return await _repository.GetByIdAsync(id); // Extra state machine
}
// BETTER: Direct return when no additional work
public Task<Customer> GetCustomer(int id)
{
return _repository.GetByIdAsync(id); // No state machine overhead
}
// USE ASYNC WHEN: You need try/catch, using, or multiple awaits
public async Task<Customer> GetCustomerWithLogging(int id)
{
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get customer {Id}", id);
throw;
}
}
Chapter 12: Entity Framework Core
12.1 Query Optimization
AsNoTracking for Read-Only Queries
// BAD: Tracking enabled by default (unnecessary for reads)
public async Task<List<Customer>> GetAllCustomersBad()
{
return await _context.[Link](); // Tracking overhead
}
// GOOD: Disable tracking for read-only queries
public async Task<List<Customer>> GetAllCustomersGood()
{
return await _context.Customers
.AsNoTracking() // 20-30% faster, less memory
.ToListAsync();
}
// Or configure at DbContext level for all queries
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
[Link]([Link]);
}
Projection with Select (Only Needed Columns)
// BAD: Fetches all columns
public async Task<List<CustomerListItem>> GetCustomerListBad()
{
var customers = await _context.[Link]();
return [Link](c => new CustomerListItem([Link], [Link], [Link])).ToList();
}
// GOOD: Database-level projection
public async Task<List<CustomerListItem>> GetCustomerListGood()
{
return await _context.Customers
.Select(c => new CustomerListItem([Link], [Link], [Link])) // Only 3 columns
.ToListAsync();
}
// Generated SQL:
// SELECT Id, Name, Email FROM Customers (vs SELECT * FROM Customers)
Avoiding N+1 Queries with Include
// BAD: N+1 problem (1 query + N queries for orders)
public async Task<List<Customer>> GetCustomersWithOrdersBad()
{
var customers = await _context.[Link]();
foreach (var customer in customers)
{
// Lazy loading triggers separate query for EACH customer!
var orders = [Link]; // N additional queries
}
return customers;
}
// GOOD: Eager loading with Include
public async Task<List<Customer>> GetCustomersWithOrdersGood()
{
return await _context.Customers
.Include(c => [Link]) // Single JOIN query
.ToListAsync();
}
// BETTER: Split query for large includes (avoids Cartesian explosion)
public async Task<List<Customer>> GetCustomersWithOrdersSplit()
{
return await _context.Customers
.Include(c => [Link])
.ThenInclude(o => [Link])
.AsSplitQuery() // Multiple optimized queries
.ToListAsync();
}
12.2 Pagination Strategies
Offset-Based Pagination
public async Task<PagedResult<Customer>> GetPagedCustomersAsync(
int page,
int pageSize,
CancellationToken cancellationToken = default)
{
var query = _context.[Link]();
var totalCount = await [Link](cancellationToken);
var items = await query
.OrderBy(c => [Link]) // Consistent ordering required!
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
return new PagedResult<Customer>(items, page, pageSize, totalCount);
}
Keyset Pagination (Better for Large Datasets)
// More efficient for deep pagination (page 1000+)
public async Task<List<Customer>> GetCustomersAfterAsync(
int lastId,
int pageSize,
CancellationToken cancellationToken = default)
{
return await _context.Customers
.AsNoTracking()
.Where(c => [Link] > lastId) // Uses index efficiently
.OrderBy(c => [Link])
.Take(pageSize)
.ToListAsync(cancellationToken);
}
// Comparison:
// Offset: SELECT * FROM Customers ORDER BY Id OFFSET 10000 ROWS FETCH NEXT 10
// → Database must scan 10,000 rows to skip them
// Keyset: SELECT TOP 10 * FROM Customers WHERE Id > 10000 ORDER BY Id
// → Direct index seek, much faster
12.3 Bulk Operations (.NET 7+)
// BAD: Loads entities into memory, then updates
public async Task DeactivateInactiveCustomersBad()
{
var inactiveCustomers = await _context.Customers
.Where(c => [Link] < [Link](-1))
.ToListAsync();
foreach (var customer in inactiveCustomers)
{
[Link] = false;
}
await _context.SaveChangesAsync(); // N update statements
}
// GOOD: Bulk update without loading entities
public async Task DeactivateInactiveCustomersGood()
{
await _context.Customers
.Where(c => [Link] < [Link](-1))
.ExecuteUpdateAsync(c => [Link](x => [Link], false));
// Single UPDATE statement, no entity loading
}
// Bulk delete
public async Task DeleteOldOrdersAsync()
{
await _context.Orders
.Where(o => [Link] < [Link](-5))
.ExecuteDeleteAsync();
// Single DELETE statement
}
12.4 Compiled Queries for Hot Paths
public class CustomerRepository : ICustomerRepository
{
// Compiled query (query plan cached)
private static readonly Func<AppDbContext, int, Task<Customer?>> _getByIdQuery =
[Link]((AppDbContext context, int id) =>
[Link](c => [Link] == id));
private static readonly Func<AppDbContext, string, Task<Customer?>> _getByEmailQuery =
[Link]((AppDbContext context, string email) =>
[Link](c => [Link] == email));
private readonly AppDbContext _context;
public CustomerRepository(AppDbContext context)
{
_context = context;
}
public Task<Customer?> GetByIdAsync(int id)
{
return _getByIdQuery(_context, id); // No query compilation overhead
}
public Task<Customer?> GetByEmailAsync(string email)
{
return _getByEmailQuery(_context, email);
}
}
12.5 Entity Configuration (Fluent API)
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
// Table name
[Link]("Customers");
// Primary key
[Link](c => [Link]);
// Properties
[Link](c => [Link])
.IsRequired()
.HasMaxLength(100);
[Link](c => [Link])
.IsRequired()
.HasMaxLength(255);
// Indexes
[Link](c => [Link])
.IsUnique();
[Link](c => new { [Link], [Link] });
// Relationships
[Link](c => [Link])
.WithOne(o => [Link])
.HasForeignKey(o => [Link])
.OnDelete([Link]);
// Value conversion
[Link](c => [Link])
.HasConversion<string>();
}
}
// Apply configurations in DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
[Link](typeof(AppDbContext).Assembly);
}
Chapter 13: Dependency Injection
13.1 Service Lifetimes
Understanding Lifetimes
Lifetime Behavior Use Case
**Transient** New instance per request Lightweight, stateless services
**Scoped** One instance per HTTP request DbContext, Unit of Work
**Singleton** One instance for app lifetime Configuration, Caching, Logging
Lifetime Examples
var builder = [Link](args);
// Transient: New instance every time requested
[Link]<IEmailFormatter, EmailFormatter>();
// Scoped: One instance per HTTP request
[Link]<ICustomerRepository, CustomerRepository>();
[Link]<IUnitOfWork, UnitOfWork>();
[Link]<AppDbContext>(); // Scoped by default
// Singleton: One instance for entire application
[Link]<IConfiguration>([Link]);
[Link]<ICacheService, MemoryCacheService>();
13.2 Captive Dependency Problem
Never inject Scoped services into Singleton services!
// BAD: Captive dependency - DbContext is "captured" by singleton
public class BadCacheService // Registered as Singleton
{
private readonly AppDbContext _context; // Scoped - will never be disposed!
public BadCacheService(AppDbContext context) // DON'T DO THIS
{
_context = context;
}
}
// GOOD: Use IServiceScopeFactory for scoped services in singletons
public class GoodCacheService : ICacheService // Registered as Singleton
{
private readonly IServiceScopeFactory _scopeFactory;
public GoodCacheService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task<Customer?> GetCustomerAsync(int id)
{
// Create scope when needed
await using var scope = _scopeFactory.CreateAsyncScope();
var context = [Link]<AppDbContext>();
return await [Link](id);
} // Scope disposed, DbContext properly cleaned up
}
Enable Scope Validation in Development
var builder = [Link](args);
if ([Link]())
{
[Link](options =>
{
[Link] = true; // Detect captive dependencies
[Link] = true; // Detect missing registrations at startup
});
}
13.3 Registration Patterns
Interface-Based Registration
// Single implementation
[Link]<ICustomerService, CustomerService>();
// Multiple implementations with different interfaces
[Link]<IEmailSender, SmtpEmailSender>();
[Link]<ISmsSender, TwilioSmsSender>();
Keyed Services (.NET 8+)
// Register multiple implementations with keys
[Link]<ICache, MemoryCache>("memory");
[Link]<ICache, RedisCache>("redis");
// Inject by key
public class MyService
{
private readonly ICache _cache;
public MyService([FromKeyedServices("redis")] ICache cache)
{
_cache = cache;
}
}
Factory-Based Registration
[Link]<IPaymentProcessor>(sp =>
{
var config = [Link]<IConfiguration>();
var environment = config["Environment"];
return environment == "Production"
? new StripePaymentProcessor(config["Stripe:ApiKey"]!)
: new MockPaymentProcessor();
});
Options Pattern for Configuration
// Configuration class
public class EmailSettings
{
public const string SectionName = "Email";
public required string SmtpServer { get; init; }
public required int Port { get; init; }
public required string FromAddress { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
}
// Registration with validation
[Link]<EmailSettings>()
.BindConfiguration([Link])
.ValidateDataAnnotations()
.ValidateOnStart(); // Fail fast if config is invalid
// Usage
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> options)
{
_settings = [Link];
}
}
13.4 Primary Constructors (C# 12)
// Traditional constructor injection
public class CustomerService : ICustomerService
{
private readonly ICustomerRepository _repository;
private readonly ILogger<CustomerService> _logger;
private readonly IEmailService _emailService;
public CustomerService(
ICustomerRepository repository,
ILogger<CustomerService> logger,
IEmailService emailService)
{
_repository = repository;
_logger = logger;
_emailService = emailService;
}
public async Task<Customer?> GetByIdAsync(int id)
{
return await _repository.GetByIdAsync(id);
}
}
// With primary constructor (C# 12) - Much cleaner!
public class CustomerService(
ICustomerRepository repository,
ILogger<CustomerService> logger,
IEmailService emailService) : ICustomerService
{
public async Task<Customer?> GetByIdAsync(int id)
{
return await [Link](id);
}
public async Task<Customer> CreateAsync(CreateCustomerRequest request)
{
var customer = new Customer([Link], [Link]);
await [Link](customer);
await [Link]([Link]);
[Link]("Customer {Id} created", [Link]);
return customer;
}
}
Chapter 14: Error Handling
14.1 Result Pattern vs Exceptions
When to Use Each Approach
Scenario Use Result Pattern Use Exceptions
Validation failure ✅
Resource not found ✅
Business rule violation ✅
Database connection error ✅
Network timeout ✅
Unexpected runtime error ✅
Out of memory ✅
Result Pattern Implementation
// Simple Result type
public class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
private Result(bool isSuccess, T? value, string? error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public static Result<T> Success(T value) => new(true, value, null);
public static Result<T> Failure(string error) => new(false, default, error);
}
// Using Result pattern
public class CustomerService
{
public async Task<Result<Customer>> GetByIdAsync(int id)
{
if (id <= 0)
return Result<Customer>.Failure("Invalid customer ID");
var customer = await _repository.GetByIdAsync(id);
if (customer is null)
return Result<Customer>.Failure($"Customer with ID {id} not found");
return Result<Customer>.Success(customer);
}
public async Task<Result<Customer>> CreateAsync(CreateCustomerRequest request)
{
// Validation
if ([Link]([Link]))
return Result<Customer>.Failure("Name is required");
if (await _repository.ExistsByEmailAsync([Link]))
return Result<Customer>.Failure("Email already registered");
// Business logic
var customer = new Customer([Link], [Link]);
await _repository.AddAsync(customer);
return Result<Customer>.Success(customer);
}
}
// Controller handling Result
[HttpGet("{id}")]
public async Task<ActionResult<CustomerDto>> GetById(int id)
{
var result = await _customerService.GetByIdAsync(id);
if (![Link])
return NotFound([Link]);
return Ok([Link]([Link]!));
}
Using ErrorOr Library
// Install: dotnet add package ErrorOr
public async Task<ErrorOr<Customer>> CreateCustomerAsync(CreateCustomerRequest request)
{
if ([Link]([Link]))
return [Link]("[Link]", "Name is required");
if (await _repository.ExistsByEmailAsync([Link]))
return [Link]("[Link]", "Email already registered");
var customer = new Customer([Link], [Link]);
await _repository.AddAsync(customer);
return customer;
}
// Controller
[HttpPost]
public async Task<IActionResult> Create(CreateCustomerRequest request)
{
var result = await _customerService.CreateCustomerAsync(request);
return [Link](
customer => CreatedAtAction(nameof(GetById), new { id = [Link] }, customer),
errors => Problem(errors));
}
14.2 Global Exception Handling
Exception Handling Middleware
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "Unhandled exception occurred");
var (statusCode, title) = exception switch
{
ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"),
NotFoundException => (StatusCodes.Status404NotFound, "Not Found"),
UnauthorizedAccessException => (StatusCodes.Status401Unauthorized,
"Unauthorized"),
_ => (StatusCodes.Status500InternalServerError, "Server Error")
};
[Link] = statusCode;
await [Link](new ProblemDetails
{
Status = statusCode,
Title = title,
Detail = [Link],
Instance = [Link]
}, cancellationToken);
return true;
}
}
// Registration
[Link]<GlobalExceptionHandler>();
[Link]();
// In pipeline
[Link]();
14.3 Exception Best Practices
// DO: Use specific exception types
public async Task<Customer> GetByIdAsync(int id)
{
if (id <= 0)
throw new ArgumentOutOfRangeException(nameof(id), "ID must be positive");
var customer = await _repository.GetByIdAsync(id);
if (customer is null)
throw new CustomerNotFoundException(id);
return customer;
}
// DO: Preserve stack trace
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex)
{
_logger.LogError(ex, "Order processing failed");
throw; // CORRECT: preserves stack trace
// throw ex; // WRONG: loses original stack trace
}
// DON'T: Use exceptions for flow control
// BAD
public bool TryGetCustomer(int id, out Customer customer)
{
try
{
customer = GetById(id); // Throws if not found
return true;
}
catch (NotFoundException)
{
customer = null;
return false;
}
}
// GOOD
public Customer? TryGetCustomer(int id)
{
return _repository.GetByIdOrDefault(id); // Returns null if not found
}
Chapter 15: Input Validation
15.1 Data Annotations (Simple Cases)
public record CreateCustomerRequest
{
[Required(ErrorMessage = "Name is required")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "Name must be 2-100 characters")]
public required string Name { get; init; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
public required string Email { get; init; }
[Phone(ErrorMessage = "Invalid phone format")]
public string? Phone { get; init; }
[Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
public int? Age { get; init; }
[RegularExpression(@"^\d{5}(-\d{4})?$", ErrorMessage = "Invalid ZIP code")]
public string? ZipCode { get; init; }
}
15.2 FluentValidation (Complex Cases)
Installation and Setup
<!-- Required packages -->
<PackageReference Include="FluentValidation" Version="11.*" />
<PackageReference Include="[Link]" Version="11.*"
/>
<!-- Note: DO NOT use [Link] - it's deprecated! -->
// Registration
[Link]<CreateCustomerValidator>();
Validator Implementation
public class CreateCustomerValidator : AbstractValidator<CreateCustomerRequest>
{
private readonly ICustomerRepository _customerRepository;
public CreateCustomerValidator(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
RuleFor(x => [Link])
.NotEmpty().WithMessage("Name is required")
.MaximumLength(100).WithMessage("Name cannot exceed 100 characters")
.Must(BeValidName).WithMessage("Name contains invalid characters");
RuleFor(x => [Link])
.NotEmpty().WithMessage("Email is required")
.EmailAddress().WithMessage("Invalid email format")
.MustAsync(BeUniqueEmail).WithMessage("Email is already registered");
RuleFor(x => [Link])
.Matches(@"^\+?[\d\s\-\(\)]+$")
.When(x => )
.WithMessage("Invalid phone format");
RuleFor(x => [Link])
.LessThan([Link](-18))
.WithMessage("Must be at least 18 years old");
}
private bool BeValidName(string name)
{
return );
}
private async Task<bool> BeUniqueEmail(string email, CancellationToken
cancellationToken)
{
return !await _customerRepository.ExistsByEmailAsync(email, cancellationToken);
}
}
Manual Validation in Controller
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private readonly ICustomerService _customerService;
private readonly IValidator<CreateCustomerRequest> _validator;
public CustomersController(
ICustomerService customerService,
IValidator<CreateCustomerRequest> validator)
{
_customerService = customerService;
_validator = validator;
}
[HttpPost]
public async Task<IActionResult> Create(
CreateCustomerRequest request,
CancellationToken cancellationToken)
{
// Manual validation
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (![Link])
{
return ValidationProblem([Link]());
}
var customer = await _customerService.CreateAsync(request, cancellationToken);
return CreatedAtAction(nameof(GetById), new { id = [Link] }, customer);
}
}
Validation Endpoint Filter (Minimal APIs)
public class ValidationFilter<T> : IEndpointFilter where T : class
{
private readonly IValidator<T> _validator;
public ValidationFilter(IValidator<T> validator)
{
_validator = validator;
}
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var argument = [Link]<T>().FirstOrDefault();
if (argument is null)
return [Link]("Invalid request body");
var result = await _validator.ValidateAsync(argument);
if (![Link])
return [Link]([Link]());
return await next(context);
}
}
// Usage
[Link]("/api/customers", CreateCustomer)
.AddEndpointFilter<ValidationFilter<CreateCustomerRequest>>();
MediatR Validation Pipeline
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest,
TResponse>
where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
return await next();
var context = new ValidationContext<TRequest>(request);
var validationResults = await [Link](
_validators.Select(v => [Link](context, cancellationToken)));
var failures = validationResults
.SelectMany(r => [Link])
.Where(f => f != null)
.ToList();
if ([Link] != 0)
throw new ValidationException(failures);
return await next();
}
}
Chapter 16: Security
16.1 Security Headers
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Add headers before response starts
[Link](() =>
{
var headers = [Link];
// Prevent MIME type sniffing
headers["X-Content-Type-Options"] = "nosniff";
// Prevent clickjacking
headers["X-Frame-Options"] = "DENY";
// Disable deprecated XSS filter (can cause vulnerabilities)
headers["X-XSS-Protection"] = "0";
// Control referrer information
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
// Content Security Policy (customize as needed)
headers["Content-Security-Policy"] =
"default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"font-src 'self'; " +
"connect-src 'self'; " +
"frame-ancestors 'none';";
// Enforce HTTPS
headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
return [Link];
});
await _next(context);
}
}
// Registration
[Link]<SecurityHeadersMiddleware>();
16.2 SQL Injection Prevention
// BAD: Vulnerable to SQL injection
public async Task<Customer?> GetByEmailBad(string email)
{
var sql = $"SELECT * FROM Customers WHERE Email = '{email}'"; // NEVER DO THIS!
return await _context.[Link](sql).FirstOrDefaultAsync();
}
// Attacker input: "'; DROP TABLE Customers; --"
// GOOD: Use EF Core (parameterized by default)
public async Task<Customer?> GetByEmailGood(string email)
{
return await _context.Customers
.FirstOrDefaultAsync(c => [Link] == email); // Safe - parameterized
}
// GOOD: Use parameterized raw SQL if needed
public async Task<Customer?> GetByEmailRaw(string email)
{
return await _context.Customers
.FromSqlInterpolated($"SELECT * FROM Customers WHERE Email = {email}")
.FirstOrDefaultAsync(); // {email} is automatically parameterized
}
// GOOD: Explicit parameters
public async Task<List<Customer>> SearchCustomers(string searchTerm)
{
return await _context.Customers
.FromSqlRaw(
"SELECT * FROM Customers WHERE Name LIKE @term",
new SqlParameter("@term", $"%{searchTerm}%"))
.ToListAsync();
}
16.3 XSS Prevention
// Razor automatically encodes output (safe by default)
<p>@[Link]</p> <!-- Encoded: <script> becomes <script> -->
// Only use [Link] for trusted, sanitized content
@[Link]([Link])
// For API responses, return data, not HTML
[HttpGet("{id}")]
public async Task<ActionResult<CustomerDto>> GetById(int id)
{
var customer = await _service.GetByIdAsync(id);
return Ok(customer); // Return data, frontend handles display
}
// If you must accept HTML, use a sanitization library
public class CommentService
{
private readonly HtmlSanitizer _sanitizer = new();
public string SanitizeComment(string userHtml)
{
return _sanitizer.Sanitize(userHtml); // Removes dangerous tags/attributes
}
}
16.4 Authentication & Authorization
// JWT Configuration
[Link]([Link])
.AddJwtBearer(options =>
{
[Link] = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = [Link]["Jwt:Issuer"],
ValidAudience = [Link]["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
[Link]([Link]["Jwt:Key"]!)),
ClockSkew = [Link] // No tolerance for expired tokens
};
});
[Link](options =>
{
[Link]("AdminOnly", policy =>
[Link]("Admin"));
[Link]("CanManageOrders", policy =>
[Link]("Permission", "[Link]"));
});
// Controller usage
[ApiController]
[Route("api/[controller]")]
[Authorize] // Requires authentication
public class OrdersController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<List<Order>>> GetAll() { ... }
[HttpDelete("{id}")]
[Authorize(Policy = "AdminOnly")] // Requires Admin role
public async Task<IActionResult> Delete(int id) { ... }
}
16.5 Secret Management
// NEVER DO THIS
public class AppSettings
{
public string DbConnectionString = "Server=...;Password=supersecret123"; // EXPOSED!
public string ApiKey = "sk_live_12345"; // EXPOSED!
}
// Development: User Secrets
// Run: dotnet user-secrets set "Database:Password" "mypassword"
// Access: [Link]["Database:Password"]
// Production: Environment Variables or Azure Key Vault
[Link]();
// Azure Key Vault
[Link](
new Uri($"[Link]
new DefaultAzureCredential());
// Access secrets through configuration
public class DatabaseSettings
{
public required string ConnectionString { get; init; } // From Key Vault
}
[Link]<DatabaseSettings>()
.BindConfiguration("Database")
.ValidateOnStart();
Chapter 17: Performance Optimization
17.1 Caching Strategies
HybridCache (.NET 9+)
// Registration
[Link](options =>
{
[Link] = new HybridCacheEntryOptions
{
Expiration = [Link](30),
LocalCacheExpiration = [Link](5)
};
});
// Add distributed cache backend (e.g., Redis)
[Link](options =>
{
[Link] = "localhost:6379";
});
// Usage
public class CustomerService(HybridCache cache, ICustomerRepository repository)
{
public async Task<Customer?> GetByIdAsync(int id, CancellationToken ct)
{
return await [Link](
$"customer_{id}",
async token => await [Link](id, token),
cancellationToken: ct);
}
// With tags for invalidation
public async Task<Customer?> GetByIdWithTagsAsync(int id, CancellationToken ct)
{
return await [Link](
$"customer_{id}",
async token => await [Link](id, token),
tags: ["customers", $"customer_{id}"],
cancellationToken: ct);
}
// Invalidate all customers
public async Task InvalidateAllCustomersAsync(CancellationToken ct)
{
await [Link]("customers", ct);
}
}
Memory Cache (Simple Scenarios)
public class ProductService(IMemoryCache cache, IProductRepository repository)
{
private static readonly MemoryCacheEntryOptions _cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = [Link](30),
SlidingExpiration = [Link](10)
};
public async Task<Product?> GetByIdAsync(int id)
{
var cacheKey = $"product_{id}";
if ()
{
product = await [Link](id);
if (product is not null)
{
[Link](cacheKey, product, _cacheOptions);
}
}
return product;
}
}
17.2 Rate Limiting (.NET 7+)
// Registration
[Link](options =>
{
// Global fixed window limiter
[Link] = [Link]<HttpContext, string>(context =>
[Link](
partitionKey: [Link]?.Identity?.Name ??
[Link](),
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = [Link](1)
}));
// Named policy for specific endpoints
[Link]("api", limiterOptions =>
{
[Link] = 50;
[Link] = [Link](1);
[Link] = [Link];
[Link] = 5;
});
// Token bucket for bursting
[Link]("burst", limiterOptions =>
{
[Link] = 100;
[Link] = 20;
[Link] = [Link](10);
});
// Custom response for rate limited requests
[Link] = async (context, ct) =>
{
[Link] = StatusCodes.Status429TooManyRequests;
await [Link](new
{
error = "Too many requests",
retryAfter = [Link]([Link], out var
retryAfter)
? [Link]
: 60
}, ct);
};
});
// Usage in pipeline
[Link]();
// Apply to endpoints
[Link]("/api/customers", GetCustomers)
.RequireRateLimiting("api");
17.3 Memory Optimization
ArrayPool for Large Arrays
public class DataProcessor
{
public async Task ProcessLargeDataAsync(Stream stream)
{
// Rent buffer from pool instead of allocating
byte[] buffer = ArrayPool<byte>.[Link](81920);
try
{
int bytesRead;
while ((bytesRead = await [Link](buffer)) > 0)
{
ProcessChunk([Link](0, bytesRead));
}
}
finally
{
ArrayPool<byte>.[Link](buffer); // Return to pool
}
}
}
Span<T> for Zero-Allocation Slicing
public class StringParser
{
public (string Key, string Value) ParseKeyValue(ReadOnlySpan<char> input)
{
int separatorIndex = [Link]('=');
if (separatorIndex < 0)
throw new FormatException("Invalid format");
var key = input[..separatorIndex]; // No allocation
var value = input[(separatorIndex + 1)..]; // No allocation
return ([Link](), [Link]());
}
}
Response Streaming for Large Data
[HttpGet("export")]
public async Task ExportLargeDataset(CancellationToken cancellationToken)
{
[Link] = "application/json";
await using var writer = new Utf8JsonWriter([Link]);
[Link]();
await foreach (var item in _service.GetAllAsync(cancellationToken))
{
[Link](writer, item);
await [Link](cancellationToken); // Stream to client
}
[Link]();
}
17.4 Response Compression
[Link](options =>
{
[Link] = true;
[Link]<BrotliCompressionProvider>();
[Link]<GzipCompressionProvider>();
});
[Link]<BrotliCompressionProviderOptions>(options =>
{
[Link] = [Link];
});
// Pipeline (early in the middleware)
[Link]();
Chapter 18: .NET 8/9 Modern Features
18.1 Primary Constructors
// Before (verbose)
public class CustomerService : ICustomerService
{
private readonly ICustomerRepository _repository;
private readonly ILogger<CustomerService> _logger;
private readonly IEmailService _emailService;
public CustomerService(
ICustomerRepository repository,
ILogger<CustomerService> logger,
IEmailService emailService)
{
_repository = repository;
_logger = logger;
_emailService = emailService;
}
}
// After (concise with primary constructor)
public class CustomerService(
ICustomerRepository repository,
ILogger<CustomerService> logger,
IEmailService emailService) : ICustomerService
{
public async Task<Customer?> GetByIdAsync(int id)
{
[Link]("Getting customer {Id}", id);
return await [Link](id);
}
}
18.2 Collection Expressions
// Before
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
Dictionary<string, int> ages = new Dictionary<string, int>
{
{ "Alice", 30 },
{ "Bob", 25 }
};
// After (collection expressions)
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
// Spread operator
int[] moreNumbers = [0, ..numbers, 6, 7, 8]; // [0, 1, 2, 3, 4, 5, 6, 7, 8]
// Empty collections
List<Customer> customers = [];
18.3 Keyed Services
// Registration
[Link]<INotificationService,
EmailNotificationService>("email");
[Link]<INotificationService, SmsNotificationService>("sms");
[Link]<INotificationService, PushNotificationService>("push");
// Constructor injection with attribute
public class OrderService(
[FromKeyedServices("email")] INotificationService emailNotifier,
[FromKeyedServices("sms")] INotificationService smsNotifier)
{
public async Task NotifyCustomerAsync(Order order)
{
await [Link]([Link], "Order confirmed");
await [Link]([Link], "Order confirmed");
}
}
// Dynamic resolution
public class NotificationRouter(IServiceProvider serviceProvider)
{
public INotificationService GetNotifier(string channel)
{
return [Link]<INotificationService>(channel);
}
}
18.4 TimeProvider for Testability
// Registration
[Link]([Link]);
// Usage in service
public class SubscriptionService(TimeProvider timeProvider)
{
public bool IsSubscriptionActive(Subscription subscription)
{
var now = [Link]();
return [Link] > now;
}
public DateTimeOffset GetRenewalDate(Subscription subscription)
{
return [Link](-7);
}
}
// Testing with fake time
public class SubscriptionServiceTests
{
[Fact]
public void IsSubscriptionActive_WhenExpired_ReturnsFalse()
{
// Arrange
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2024, 6, 15, 0, 0, 0,
[Link]));
var service = new SubscriptionService(fakeTime);
var subscription = new Subscription { ExpiresAt = new DateTimeOffset(2024, 6, 1, 0,
0, 0, [Link]) };
// Act
var result = [Link](subscription);
// Assert
[Link](result);
}
}
18.5 FrozenDictionary/FrozenSet
// For read-heavy lookup tables that don't change
public class CountryService
{
private static readonly FrozenDictionary<string, string> _countryCodes =
new Dictionary<string, string>
{
["US"] = "United States",
["GB"] = "United Kingdom",
["DE"] = "Germany",
["FR"] = "France",
// ... hundreds more
}.ToFrozenDictionary();
public string? GetCountryName(string code)
{
return _countryCodes.TryGetValue(code, out var name) ? name : null;
}
}
// FrozenSet for fast lookups
public class PermissionService
{
private static readonly FrozenSet<string> _adminPermissions =
new HashSet<string> { "[Link]", "[Link]", "[Link]", "[Link]" }
.ToFrozenSet();
public bool IsAdminPermission(string permission)
{
return _adminPermissions.Contains(permission);
}
}
18.6 MapStaticAssets (.NET 9)
// Before (.NET 8)
[Link]();
// After (.NET 9) - Better compression, caching
[Link]().ShortCircuit();
// Benefits:
// - Build-time compression (gzip in dev, gzip+Brotli in publish)
// - SHA-256 content-based ETags
// - Up to 80% size reduction
// - ShortCircuit() prevents running full middleware pipeline
Chapter 19: Observability & Monitoring
19.1 Recommended Stack
For Monolith Applications
┌─────────────┐ ┌─────────┐ ┌─────────┐
│ Your App │────>│ Loki │────>│ Grafana │ (Logs)
│ │ └─────────┘ └─────────┘
│ Serilog │ ┌────────────┐ ┌─────────┐
│ + │────>│ Prometheus │──>│ Grafana │ (Metrics)
│prometheus-net └────────────┘ └─────────┘
└─────────────┘
For Microservices (Add Tracing)
┌─────────────┐ ┌─────────┐ ┌─────────┐
│ Your App │────>│ Loki │────>│ Grafana │ (Logs)
│ │ └─────────┘ └─────────┘
│ Serilog │ ┌────────────┐ ┌─────────┐
│ + │────>│ Prometheus │──>│ Grafana │ (Metrics)
│prometheus-net └────────────┘ └─────────┘
│ + │ ┌─────────┐ ┌─────────┐
│OpenTelemetry│────>│ Tempo │────>│ Grafana │ (Traces)
└─────────────┘ └─────────┘ └─────────┘
19.2 Serilog Setup
Installation
<PackageReference Include="[Link]" Version="8.*" />
<PackageReference Include="[Link]" Version="8.*" />
Configuration
// [Link]
var builder = [Link](args);
[Link]((context, configuration) =>
{
configuration
.[Link]([Link])
.[Link]()
.[Link]()
.[Link]()
.[Link]()
.[Link](
"[Link]
labels: new[]
{
new LokiLabel { Key = "app", Value = "my-api" },
new LokiLabel { Key = "environment", Value =
[Link] }
});
});
var app = [Link]();
// Add request logging
[Link](options =>
{
[Link] = (diagnosticContext, httpContext) =>
{
[Link]("UserId", [Link]?.Identity?.Name ?? "anonymous");
[Link]("RequestHost", [Link]);
};
});
Structured Logging
public class OrderService(ILogger<OrderService> logger)
{
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// DO: Use structured logging with message templates
[Link](
"Creating order for customer {CustomerId} with {ItemCount} items",
[Link],
[Link]);
// DON'T: String interpolation loses structure
// [Link]($"Creating order for customer {[Link]}");
try
{
var order = await ProcessOrderAsync(request);
[Link](
"Order {OrderId} created successfully. Total: {OrderTotal:C}",
[Link],
[Link]);
return order;
}
catch (Exception ex)
{
[Link](
ex,
"Failed to create order for customer {CustomerId}",
[Link]);
throw;
}
}
}
19.3 Prometheus Metrics
Installation
<PackageReference Include="[Link]" Version="8.*" />
Configuration
var builder = [Link](args);
var app = [Link]();
// Expose metrics endpoint
[Link](); // Automatic HTTP metrics
[Link](); // /metrics endpoint
[Link]();
Custom Metrics
public class OrderMetrics
{
private static readonly Counter _ordersCreated = [Link](
"orders_created_total",
"Total number of orders created",
labelNames: new[] { "status" });
private static readonly Histogram _orderProcessingDuration = [Link](
"order_processing_duration_seconds",
"Time to process an order",
new HistogramConfiguration
{
Buckets = [Link](0.01, 2, 10)
});
private static readonly Gauge _activeOrders = [Link](
"active_orders_count",
"Number of orders being processed");
public void RecordOrderCreated(string status)
{
_ordersCreated.WithLabels(status).Inc();
}
public IDisposable MeasureProcessingTime()
{
_activeOrders.Inc();
return _orderProcessingDuration.NewTimer();
}
public void OrderCompleted()
{
_activeOrders.Dec();
}
}
19.4 OpenTelemetry Tracing (Microservices)
<PackageReference Include="[Link]" Version="1.*" />
<PackageReference Include="[Link]" Version="1.*" />
<PackageReference Include="[Link]" Version="1.*" />
<PackageReference Include="[Link]" Version="1.*" />
[Link]()
.WithTracing(tracing =>
{
tracing
.AddSource("MyApp")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
[Link] = new Uri("[Link]
});
});
// Custom spans
public class OrderService(ActivitySource activitySource)
{
public async Task<Order> ProcessOrderAsync(Order order)
{
using var activity = [Link]("ProcessOrder");
activity?.SetTag("[Link]", [Link]);
activity?.SetTag("[Link]", [Link]);
// Processing logic...
activity?.SetStatus([Link]);
return order;
}
}
19.5 Health Checks
[Link]()
.AddDbContextCheck<AppDbContext>("database")
.AddRedis("redis-connection-string", name: "redis")
.AddUrlGroup(new Uri("[Link] name: "external-api");
[Link]("/health", new HealthCheckOptions
{
ResponseWriter = [Link]
});
[Link]("/health/ready", new HealthCheckOptions
{
Predicate = check => [Link]("ready")
});
[Link]("/health/live", new HealthCheckOptions
{
Predicate = _ => false // Just checks if app responds
});
Chapter 20: Testing
20.1 Test Naming Convention
Method_Scenario_ExpectedBehavior
Examples:
- GetCustomerById_WhenCustomerExists_ReturnsCustomer
- GetCustomerById_WhenCustomerNotFound_ReturnsNull
- CreateOrder_WhenCartIsEmpty_ThrowsInvalidOperationException
- CalculateDiscount_WhenCustomerIsPremium_Returns20Percent
20.2 Unit Testing with Arrange-Act-Assert
public class CustomerServiceTests
{
private readonly Mock<ICustomerRepository> _repositoryMock;
private readonly Mock<ILogger<CustomerService>> _loggerMock;
private readonly CustomerService _sut; // System Under Test
public CustomerServiceTests()
{
_repositoryMock = new Mock<ICustomerRepository>();
_loggerMock = new Mock<ILogger<CustomerService>>();
_sut = new CustomerService(_repositoryMock.Object, _loggerMock.Object);
}
[Fact]
public async Task GetByIdAsync_WhenCustomerExists_ReturnsCustomer()
{
// Arrange
var customerId = 1;
var expectedCustomer = new Customer { Id = customerId, Name = "John Doe" };
_repositoryMock
.Setup(r => [Link](customerId, [Link]<CancellationToken>()))
.ReturnsAsync(expectedCustomer);
// Act
var result = await _sut.GetByIdAsync(customerId);
// Assert
[Link](result);
[Link]([Link], [Link]);
[Link]([Link], [Link]);
}
[Fact]
public async Task GetByIdAsync_WhenCustomerNotFound_ReturnsNull()
{
// Arrange
var customerId = 999;
_repositoryMock
.Setup(r => [Link](customerId, [Link]<CancellationToken>()))
.ReturnsAsync((Customer?)null);
// Act
var result = await _sut.GetByIdAsync(customerId);
// Assert
[Link](result);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-100)]
public async Task GetByIdAsync_WhenIdIsInvalid_ThrowsArgumentException(int invalidId)
{
// Act & Assert
await [Link]<ArgumentOutOfRangeException>(
() => _sut.GetByIdAsync(invalidId));
}
}
20.3 Mocking with Moq
public class OrderServiceTests
{
[Fact]
public async Task CreateOrderAsync_SendsEmailToCustomer()
{
// Arrange
var emailServiceMock = new Mock<IEmailService>();
var repositoryMock = new Mock<IOrderRepository>();
var sut = new OrderService([Link], [Link]);
var request = new CreateOrderRequest
{
CustomerId = 1,
CustomerEmail = "test@[Link]",
Items = new List<OrderItem> { new(1, 2, 10.00m) }
};
// Act
await [Link](request);
// Assert - Verify email was sent
[Link](
e => [Link](
"test@[Link]",
[Link]<string>(s => [Link]("Order Confirmation")),
[Link]<string>(),
[Link]<CancellationToken>()),
[Link]);
}
[Fact]
public async Task CreateOrderAsync_WhenEmailFails_StillSavesOrder()
{
// Arrange
var emailServiceMock = new Mock<IEmailService>();
emailServiceMock
.Setup(e => [Link]([Link]<string>(), [Link]<string>(),
[Link]<string>(), [Link]<CancellationToken>()))
.ThrowsAsync(new SmtpException("Email server unavailable"));
var repositoryMock = new Mock<IOrderRepository>();
var sut = new OrderService([Link], [Link]);
// Act
await [Link](new CreateOrderRequest { /* ... */ });
// Assert - Order was still saved
[Link](
r => [Link]([Link]<Order>(), [Link]<CancellationToken>()),
[Link]);
}
}
20.4 Integration Testing with WebApplicationFactory
public class CustomersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public CustomersApiTests(WebApplicationFactory<Program> factory)
{
_factory = [Link](builder =>
{
[Link](services =>
{
// Replace real database with in-memory
var descriptor = [Link](
d => [Link] == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
[Link](descriptor);
[Link]<AppDbContext>(options =>
[Link]("TestDb"));
});
});
_client = _factory.CreateClient();
}
[Fact]
public async Task GetCustomers_ReturnsSuccessAndCustomersList()
{
// Arrange
await SeedTestDataAsync();
// Act
var response = await _client.GetAsync("/api/customers");
// Assert
[Link]();
var customers = await [Link]<List<CustomerDto>>();
[Link](customers);
[Link](customers);
}
[Fact]
public async Task CreateCustomer_ReturnsCreatedAndCustomer()
{
// Arrange
var request = new CreateCustomerRequest
{
Name = "John Doe",
Email = "john@[Link]"
};
// Act
var response = await _client.PostAsJsonAsync("/api/customers", request);
// Assert
[Link]([Link], [Link]);
var customer = await [Link]<CustomerDto>();
[Link](customer);
[Link]([Link], [Link]);
[Link]("/api/customers/", [Link]?.ToString());
}
[Fact]
public async Task CreateCustomer_WithInvalidData_ReturnsBadRequest()
{
// Arrange
var request = new CreateCustomerRequest
{
Name = "", // Invalid: empty name
Email = "invalid-email" // Invalid: not an email
};
// Act
var response = await _client.PostAsJsonAsync("/api/customers", request);
// Assert
[Link]([Link], [Link]);
}
private async Task SeedTestDataAsync()
{
using var scope = _factory.[Link]();
var context = [Link]<AppDbContext>();
[Link](
new Customer { Name = "Test User 1", Email = "test1@[Link]" },
new Customer { Name = "Test User 2", Email = "test2@[Link]" }
);
await [Link]();
}
}
20.5 Test Coverage Guidelines
Area Target Coverage
Business Logic (Domain) 90%+
Application Services 80%+
Critical Paths 100%
Controllers 70%+
Infrastructure 50%+
Chapter 21: Code Review Checklist
21.1 Functionality
● Code works correctly for all scenarios
● Edge cases are handled (null, empty, boundary values)
● Error handling is appropriate
● No obvious bugs or logic errors
● Changes match requirements
21.2 Code Quality
● Naming conventions followed (PascalCase, _camelCase, etc.)
● Code is DRY (no unnecessary duplication)
● Single Responsibility Principle followed
● Methods are short and focused
● No magic numbers (use constants)
● No commented-out code
21.3 Performance
● No N+1 query issues (use Include/eager loading)
● AsNoTracking() used for read-only queries
● Async/await used correctly throughout
● No blocking calls (.Result, .Wait())
● Pagination for large result sets
● Appropriate caching considered
21.4 Security
● Input validated (Data Annotations or FluentValidation)
● No SQL injection vulnerabilities
● No XSS vulnerabilities
● No sensitive data in logs
● Authorization checks in place
● Secrets not hardcoded
21.5 Testing
● Unit tests written for business logic
● Test naming follows convention (Method_Scenario_Expected)
● Tests cover happy path and edge cases
● Tests are readable and maintainable
● Mocks used appropriately
21.6 Documentation
● XML docs for public APIs (if applicable)
● Complex logic has explanatory comments
● README updated if needed
21.7 .NET Specific Checks
● Records used for DTOs
● Nullable reference types handled properly
● CancellationToken passed through async chain
● IHttpClientFactory used (not new HttpClient())
● Correct DI lifetimes (no Scoped in Singleton)
● Middleware in correct order
Part II: CMMI Coding Practices
Chapter 22: Code Traceability
22.1 Purpose (CMMI: REQM, CM)
Code traceability ensures bidirectional linking from requirements to code to tests. This is critical for:
● Impact analysis when requirements change
● Verification that all requirements are implemented
● Audit trail for compliance
● CMMI Level 3 compliance
22.2 Work Item Linking
Rule: Every code change MUST link to a work item (User Story, Bug, Task).
Commit Message Format
# Format: type(scope): subject
#
# WorkItem: <ID>
feat(customer): implement customer registration
- Add registration endpoint
- Add email validation
- Add duplicate check
WorkItem: US-1234
Reviewed-by: [Link]
Branch Naming with Work Item
feature/US-1234-customer-login
bugfix/BUG-5678-null-reference-fix
task/TASK-9012-payment-refactor
hotfix/BUG-1111-critical-security-fix
22.3 Code Comments for Traceability
For complex business logic, include requirement references in code:
/// <summary>
/// Calculates discount based on customer tier.
/// </summary>
/// <remarks>
/// Requirement: REQ-2.3.1 - Discount Calculation Rules
/// Business Rule: BR-045 - Premium customers get 20% discount
/// </remarks>
public decimal CalculateDiscount(Customer customer, decimal amount)
{
// REQ-2.3.1.a: Premium tier discount
if ([Link] == [Link])
{
return amount * 0.20m;
}
// REQ-2.3.1.b: Standard tier discount
if ([Link] == [Link])
{
return amount * 0.10m;
}
return 0;
}
22.4 Traceability Matrix
Maintain a traceability matrix linking requirements to implementation:
Req ID Requirement Design Code File:Line Test Case Status
REQ-001 User login DES-001 [Link] TC-001, TC-002 Verified
REQ-002 Password reset DES-002 [Link] TC-003 In Progress
REQ-003 Email notification DES-003 [Link] TC-004 Verified
Update the matrix when:
● New code implements a requirement
● Code is refactored/moved
● Tests are added/modified
Chapter 23: Commit Standards
23.1 Purpose (CMMI: CM, MA)
Standardized commits enable:
● Automatic change tracking
● Release notes generation
● Metrics collection
● Audit compliance
23.2 Conventional Commits Format
<type>(<scope>): <subject>
[optional body]
[optional footer]
WorkItem: <ID>
Commit Types
Type Description Example
`feat` New feature `feat(auth): add JWT refresh token`
`fix` Bug fix `fix(order): resolve null reference`
`refactor` Code refactoring `refactor(payment): extract validation`
`perf` Performance improvement `perf(query): add index for search`
`test` Adding/updating tests `test(auth): add login unit tests`
`docs` Documentation `docs(api): update swagger comments`
`chore` Maintenance `chore(deps): update packages`
`security` Security fix `security(auth): fix token validation`
23.3 Commit Rules
MUST DO
✓ Link to work item (US/BUG/TASK)
✓ Use conventional commit format
✓ Keep subject under 72 characters
✓ Use imperative mood ("add" not "added")
✓ Include scope for clarity
✓ Separate subject from body with blank line
MUST NOT
✗ Commit without work item reference
✗ Use vague messages ("fix bug", "update code", "changes")
✗ Combine unrelated changes in one commit
✗ Commit commented-out code
✗ Commit secrets or sensitive data
✗ Force push to main/develop branches
23.4 Git Hooks for Enforcement
#!/bin/bash
# .git/hooks/commit-msg
# Require work item reference
if ! grep -qE "(US|BUG|TASK|REQ)-[0-9]+" "$1"; then
echo "ERROR: Commit must reference a work item (US-XXX, BUG-XXX, TASK-XXX)"
exit 1
fi
# Require conventional commit format
if ! grep -qE "^(feat|fix|refactor|perf|test|docs|chore|security)\(.+\):" "$1"; then
echo "ERROR: Use conventional commit format: type(scope): subject"
exit 1
fi
Chapter 24: Static Code Analysis
24.1 Purpose (CMMI: VER, PPQA)
Automated code analysis provides:
● Early defect detection
● Coding standards enforcement
● Security vulnerability detection
● Technical debt tracking
24.2 Required Analyzers
.NET Project Configuration
<!-- In .csproj -->
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<!-- .NET Analyzers (built-in) -->
<PackageReference Include="[Link]" Version="8.*" />
<!-- Security Analysis -->
<PackageReference Include="SecurityCodeScan.VS2019" Version="5.*" />
<!-- SonarQube Analyzers -->
<PackageReference Include="[Link]" Version="9.*" />
<!-- Additional Quality -->
<PackageReference Include="[Link]" Version="4.*" />
</ItemGroup>
EditorConfig Standards
# .editorconfig
root = true
[*.cs]
# Naming conventions - enforce as errors
dotnet_naming_rule.private_fields_should_be_camel_case.severity = error
# Code quality rules
dotnet_diagnostic.[Link] = error # Validate arguments
dotnet_diagnostic.[Link] = error # SQL injection
dotnet_diagnostic.[Link] = error # XML injection
dotnet_diagnostic.[Link] = error # Weak cryptography
24.3 SonarQube Integration
Quality Gate Criteria
Metric Threshold Action if Failed
Coverage >= 80% Block merge
Duplicated Lines < 3% Block merge
Maintainability Rating A Block merge
Reliability Rating A Block merge
Security Rating A Block merge
Blocker Issues 0 Block merge
Critical Issues 0 Block merge
CI/CD Integration
# Azure DevOps Pipeline
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQube'
scannerMode: 'MSBuild'
projectKey: '$([Link])'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
- task: DotNetCoreCLI@2
inputs:
command: 'test'
arguments: '--collect:"XPlat Code Coverage"'
- task: SonarQubeAnalyze@5
- task: SonarQubePublish@5
# Fail if quality gate fails
- script: |
if [ "$([Link])" != "OK" ]; then
echo "Quality Gate failed!"
exit 1
fi
Chapter 25: Code Metrics & Quality Gates
25.1 Purpose (CMMI: MA, QPM)
Collect and analyze code metrics for:
● Quality monitoring
● Process improvement
● Predictive analysis (Level 4-5)
25.2 Complexity Thresholds
Metric Target Action if Exceeded
Cyclomatic Complexity (method) ≤ 10 Refactor required
Cyclomatic Complexity (class) ≤ 50 Split class
Cognitive Complexity (method) ≤ 15 Simplify logic
Lines per Method ≤ 30 Extract methods
Lines per Class ≤ 500 Split class
Parameters per Method ≤5 Use parameter object
Nesting Depth ≤3 Refactor/early return
25.3 Coverage Thresholds
By Code Category
Category Minimum Target
Critical Business Logic 90% 100%
Domain Entities 85% 90%
Application Services 80% 85%
API Controllers 70% 80%
Infrastructure 60% 70%
By Risk Level
Risk Level Coverage Examples
Critical 100% Financial calculations, Security
High 90% Core business logic, Payment
Medium 80% Standard features
Low 70% Utilities, helpers
25.4 Quality Gates
PR Quality Gate
# Must pass before merge to develop
pr_quality_gate:
build: success
unit_tests: all_pass
coverage: ">= 70%"
sonar_quality_gate: pass
security_scan: no_critical_or_high
code_review: approved_by_1
Release Quality Gate
# Must pass before release to production
release_quality_gate:
all_tests: pass
coverage: ">= 80%"
critical_path_coverage: ">= 90%"
sonar_quality_gate: pass
security_scan: no_critical_or_high
performance_test: pass
uat_signoff: obtained
documentation: complete
Chapter 26: CMMI Peer Review Standards
26.1 Purpose (CMMI: VER, PPQA)
Formal peer review for:
● Defect detection before testing
● Knowledge sharing
● Standards compliance
● Audit evidence
26.2 Review Types
Type When Participants Duration
Self Review Before PR Author 15-30 min
Peer Review All PRs 1-2 reviewers 30-60 min
Technical Review Complex changes Tech Lead + 2 1-2 hours
Inspection Critical components Formal team 2-4 hours
26.3 CMMI Code Review Checklist
Functional Review
[ ] All acceptance criteria met
[ ] Edge cases handled
[ ] Error scenarios covered
[ ] Requirement traceability documented (code links to work item)
Code Quality Review
[ ] Naming conventions followed
[ ] Code formatting correct
[ ] No magic numbers/strings
[ ] No commented-out code
[ ] No TODO without work item reference
[ ] Single Responsibility followed
[ ] No code duplication
Security Review
[ ] Input validation present
[ ] No SQL injection vulnerabilities
[ ] No XSS vulnerabilities
[ ] Authentication/authorization correct
[ ] Sensitive data not logged
[ ] Secrets not hardcoded
Performance Review
[ ] Async/await used correctly
[ ] No blocking calls (.Result, .Wait())
[ ] No N+1 query issues
[ ] AsNoTracking() for read-only queries
[ ] Pagination for large datasets
Test Review
[ ] Unit tests written
[ ] Coverage adequate (>80%)
[ ] Test naming convention followed
[ ] Edge cases tested
[ ] Mocks used appropriately
26.4 Review Metrics (Track for CMMI)
Metric Purpose Target
Review Coverage % of code reviewed 100%
Review Rate Lines/hour 200-400
Defect Detection Rate Defects/KLOC reviewed Track trend
Review Turnaround PR to approval < 24 hours
Comments per Review Engagement 3-10
Rework Rate Changes after review < 20%
26.5 Review Documentation
# Code Review Record
**PR:** #1234
**Author:** [Link]
**Reviewer(s):** [Link], [Link]
**Date:** 2026-01-22
**Duration:** 45 minutes
## Files Reviewed
- [Link] (150 lines)
- [Link] (80 lines)
## Findings
| # | Type | Severity | Description | Resolution |
|---|------|----------|-------------|------------|
| 1 | Defect | Major | Missing null check | Fixed |
| 2 | Style | Minor | Naming convention | Fixed |
## Outcome: Approved
Reviewer: [Link] | Date: 2026-01-22
Chapter 27: Defect Classification (ODC)
27.1 Purpose (CMMI: CAR, MA)
Orthogonal Defect Classification (ODC) for:
● Root cause analysis
● Process improvement
● Defect prevention
● Trend analysis
27.2 ODC Defect Types
Type Description Example
**Function** Affects capability, end-user interfaces Missing feature, wrong calculation result
**Assignment** Initialization, variable assignment Wrong initial value, null assignment
**Algorithm** Logic, computation Off-by-one error, wrong formula
**Checking** Validation, error handling Missing null check, wrong validation
**Interface** API, module interaction Wrong parameter, incorrect call signature
**Timing** Concurrency, sequencing Race condition, deadlock
**Documentation** Comments, specs Misleading comment, wrong docs
27.3 Defect Qualifier
Qualifier Meaning Example
**Missing** Code/logic should exist but doesn't No null check
**Incorrect** Code exists but is wrong Wrong comparison operator
**Extraneous** Code exists but shouldn't Unnecessary validation
27.4 Defect Trigger
Trigger Phase Found
Design Review Design
Code Review Development
Unit Test Development
Integration Test Testing
System Test Testing
UAT Validation
Production Operations
27.5 Bug Report Template (CMMI-Compliant)
# Bug Report
## Basic Information
- **ID:** BUG-1234
- **Title:** Null reference exception in order processing
- **Severity:** Critical / Major / Minor / Trivial
- **Priority:** P1 / P2 / P3 / P4
## ODC Classification
- **Defect Type:** Checking
- **Qualifier:** Missing
- **Trigger:** Integration Test
- **Target:** Code
## Description
### Steps to Reproduce
1. Create order with null customer
2. Call ProcessOrder endpoint
3. System throws NullReferenceException
### Expected vs Actual
- Expected: Validation error returned
- Actual: Unhandled exception
## Root Cause Analysis
- **Root Cause:** Missing null validation for customer parameter
- **Injection Phase:** Development
- **Why Missed:** Code review checklist didn't include this scenario
## Prevention Action
- Update code review checklist to include null validation
- Add analyzer rule for null validation
27.6 Defect Analysis
Track defect distribution to identify improvement areas:
Defect Distribution by Type (Last Quarter)
────────────────────────────────────────
Checking ████████████████████ 35% → Improve validation practices
Algorithm ████████████ 20% → More thorough code review
Interface ██████████ 18% → Better API design review
Assignment ████████ 12% → NRT enforcement
Function ██████ 10% → Requirements clarity
Timing ██ 3%
Docs █ 2%
────────────────────────────────────────
Chapter 28: Defect Prevention Practices
28.1 Purpose (CMMI: CAR - Level 5)
Prevent defects by addressing root causes through:
● Pattern analysis
● Preventive actions
● Standards updates
● Team training
28.2 Null Reference Prevention
// PREVENTION 1: Nullable Reference Types
#nullable enable
public class CustomerService
{
// Compiler enforces null checks
public Customer GetCustomer(int id)
{
Customer? customer = _repository.GetById(id);
// Must check before use
if (customer is null)
throw new CustomerNotFoundException(id);
return customer;
}
}
// PREVENTION 2: Guard Clauses
public async Task<Order> CreateOrderAsync(
Customer customer,
List<OrderItem> items)
{
[Link](customer);
[Link](items);
if ([Link] == 0)
throw new ArgumentException("Order must have items", nameof(items));
// Safe to proceed
}
// PREVENTION 3: Null Object Pattern
public IDiscountStrategy GetDiscount(Customer customer)
{
return [Link] switch
{
[Link] => new PremiumDiscount(),
[Link] => new StandardDiscount(),
_ => new NoDiscount() // Never return null
};
}
28.3 SQL Injection Prevention
// BAD - Vulnerable to SQL injection
var sql = $"SELECT * FROM Customers WHERE Email = '{email}'"; // NEVER!
// GOOD - EF Core (parameterized by default)
var customer = await _context.Customers
.FirstOrDefaultAsync(c => [Link] == email);
// GOOD - Raw SQL with interpolation (parameterized)
var customer = await _context.Customers
.FromSqlInterpolated($"SELECT * FROM Customers WHERE Email = {email}")
.FirstOrDefaultAsync();
// GOOD - Dapper with parameters
var customer = await [Link]<Customer>(
"SELECT * FROM Customers WHERE Email = @Email",
new { Email = email });
28.4 N+1 Query Prevention
// BAD - N+1 queries
var customers = await _context.[Link]();
foreach (var customer in customers)
{
var orders = [Link]; // Lazy load = N separate queries!
}
// GOOD - Eager loading
var customers = await _context.Customers
.Include(c => [Link])
.ToListAsync();
// GOOD - Projection
var customerOrders = await _context.Customers
.Select(c => new {
[Link],
OrderCount = [Link]
})
.ToListAsync();
28.5 Async/Await Issues Prevention
// BAD - Blocking (deadlock risk, thread starvation)
public Customer GetCustomer(int id)
{
return _repository.GetByIdAsync(id).Result; // NEVER!
}
// GOOD - Async all the way
public async Task<Customer> GetCustomerAsync(
int id,
CancellationToken cancellationToken)
{
return await _repository.GetByIdAsync(id, cancellationToken);
}
28.6 Defect Prevention Process
┌─────────────────────────────────────────────────────┐
│ DEFECT PREVENTION CYCLE │
├─────────────────────────────────────────────────────┤
│ │
│ 1. ANALYZE → Review defect data monthly │
│ │ - By type (ODC) │
│ │ - By injection phase │
│ ▼ │
│ 2. IDENTIFY → Find patterns │
│ │ - Top defect types │
│ │ - Common root causes │
│ ▼ │
│ 3. ROOT CAUSE → 5 Whys analysis │
│ │ - Why injected? │
│ │ - Why not detected earlier? │
│ ▼ │
│ 4. ACTIONS → Define preventive measures │
│ │ - Update coding standards │
│ │ - Add analyzer rules │
│ │ - Update review checklist │
│ ▼ │
│ 5. IMPLEMENT → Deploy changes │
│ │ │
│ ▼ │
│ 6. VERIFY → Measure defect reduction │
│ │ │
│ └──────────→ Loop back to step 1 │
│ │
└─────────────────────────────────────────────────────┘
Chapter 29: Statistical Code Quality (Level
4-5)
29.1 Purpose (CMMI: OPP, QPM)
Statistical process control for:
● Quantitative management
● Predictive analysis
● Process optimization
29.2 Establishing Baselines
Collect data for 6+ sprints to establish baselines:
Defect Density Baseline:
─────────────────────────
Mean: 4.2 defects/KLOC
Standard Deviation: 1.1
UCL (Mean + 2σ): 6.4
LCL (Mean - 2σ): 2.0
Code Coverage Baseline:
─────────────────────────
Mean: 82%
Standard Deviation: 3%
UCL: 88%
LCL: 76%
Cyclomatic Complexity Baseline:
─────────────────────────
Mean: 8.5 per method
Standard Deviation: 2.3
UCL: 13.1
LCL: 3.9
29.3 Control Charts
Monitor metrics over time using control charts:
Defect Density Control Chart
^
UCL │- - - - - - - - - - - - - - - 6.4 ← Investigate if exceeded
│ ◆
│ ● ●
Mean│──●──────●──●────────●──●── 4.2 ← Normal variation
│ ●
LCL │- - - - - - - - - - - - - - - 2.0
└──────────────────────────────▶
S1 S2 S3 S4 S5 S6 S7 Sprint
Legend:
● Within limits - Common cause (normal)
◆ Outside limits - Special cause (investigate!)
29.4 Interpreting Control Charts
Pattern Meaning Action
Point outside limits Special cause variation Investigate immediately
7+ points same side of mean Process shift Investigate trend
7+ points increasing/decreasing Trend Investigate cause
All points within limits Stable process Continue monitoring
29.5 Defect Prediction Model
/// <summary>
/// Predicts defects based on code characteristics.
/// Model: Defects = α + β₁(Complexity) + β₂(Churn) + β₃(Size)
/// </summary>
public class DefectPredictionModel
{
private readonly double _intercept = 0.5;
private readonly double _complexityCoeff = 0.3;
private readonly double _churnCoeff = 0.2;
private readonly double _sizeCoeff = 0.002;
public int PredictDefects(CodeMetrics metrics)
{
double predicted =
_intercept +
(_complexityCoeff * [Link]) +
(_churnCoeff * [Link]) +
(_sizeCoeff * [Link]);
return (int)[Link](predicted);
}
}
// Usage in Sprint Planning
var metrics = await _codeAnalyzer.AnalyzeChangesAsync(plannedChanges);
var predictedDefects = _model.PredictDefects(metrics);
if (predictedDefects > _threshold)
{
_logger.LogWarning(
"High defect risk: {Predicted} defects predicted. Consider additional review.",
predictedDefects);
}
29.6 Continuous Improvement Actions
Signal Analysis Action
Defect density increasing Review defect types (ODC) Update standards, training
Coverage decreasing Check new code coverage Enforce coverage gates
Complexity increasing Identify high-complexity modules Plan refactoring sprint
Review defects decreasing Review process effective Consider reducing review scope
29.7 CMMI Level 5 Summary
Level 5 Coding Requirements Checklist:
─────────────────────────────────────
[ ] Code quality baselines established
[ ] Statistical process control (control charts) active
[ ] Defect prediction models in use
[ ] Root cause analysis → coding standard updates
[ ] Continuous improvement program documented
[ ] Metrics reviewed monthly
[ ] Actions tracked and verified
Appendix A: Quick Reference Cards
Naming Conventions Cheat Sheet
Element Convention Example
Class PascalCase `CustomerService`
Interface IPascalCase `ICustomerRepository`
Method PascalCase `GetCustomerById`
Async Method PascalCase + Async `GetCustomerByIdAsync`
Property PascalCase `FirstName`
Private Field _camelCase `_customerRepository`
Parameter camelCase `customerId`
Constant PascalCase `MaxRetryCount`
HTTP Status Codes Reference
Code Name Usage
200 OK Successful GET/PUT/PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Validation errors
401 Unauthorized Authentication required
403 Forbidden Not authorized
404 Not Found Resource not found
500 Server Error Unexpected error
Middleware Order Reference
● Exception Handler
● HSTS
● HTTPS Redirection
● Static Files
● Routing
● CORS
● Authentication
● Authorization
● Rate Limiter
● Endpoints
Appendix B: DO / DON'T Summary
✅DO
● Use records for DTOs
● Enable Nullable Reference Types
● Use async/await all the way
● Pass CancellationToken
● Use IHttpClientFactory
● Use FluentValidation (manual pattern)
● Use AsNoTracking() for read-only queries
● Configure middleware in correct order
● Use structured logging
● Implement health checks
● Use Content-Security-Policy header
● Use Result pattern for expected errors
❌DON'T
● Use .Result or .Wait()
● Use async void (except event handlers)
● Create HttpClient directly
● Store HttpContext in fields
● Inject Scoped into Singleton
● Use string concatenation for SQL
● Log passwords or PII
● Use X-XSS-Protection (deprecated)
● Use [Link] (deprecated)
● Use ConfigureAwait(false) in [Link] Core
● Skip breaking changes on upgrade
End of Document
This guideline is verified against official Microsoft documentation as of January 2026.