# Error Handling Patterns - Detailed Reference

Detailed code examples for error handling and resiliency patterns in ASP.NET Core.

## Table of Contents

1. [Exception Middleware](#exception-middleware)
2. [Domain Exceptions](#domain-exceptions)
3. [Validation with FluentValidation](#validation-with-fluentvalidation)
4. [Result Pattern](#result-pattern)
5. [Polly Resiliency](#polly-resiliency)
6. [Health Checks](#health-checks)
7. [Problem Details](#problem-details)

---

## Exception Middleware

### Complete Exception Handler

```csharp
/// <summary>
/// EN: Global exception handler middleware with detailed error mapping.
/// VI: Middleware xử lý exception toàn cục với ánh xạ lỗi chi tiết.
/// </summary>
public class ExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlerMiddleware> _logger;
    private readonly IHostEnvironment _environment;

    public ExceptionHandlerMiddleware(
        RequestDelegate next,
        ILogger<ExceptionHandlerMiddleware> logger,
        IHostEnvironment environment)
    {
        _next = next;
        _logger = logger;
        _environment = environment;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        var traceId = Activity.Current?.Id ?? context.TraceIdentifier;

        // EN: Log with appropriate level based on exception type
        // VI: Log với level phù hợp dựa trên loại exception
        LogException(ex, traceId);

        var response = CreateErrorResponse(ex, traceId);

        context.Response.StatusCode = response.StatusCode;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsJsonAsync(response.Body);
    }

    private void LogException(Exception ex, string traceId)
    {
        var message = "EN: Error occurred / VI: Đã xảy ra lỗi TraceId: {TraceId}";

        switch (ex)
        {
            case ValidationException:
            case NotFoundException:
                _logger.LogWarning(ex, message, traceId);
                break;
            case DomainException:
                _logger.LogWarning(ex, message, traceId);
                break;
            default:
                _logger.LogError(ex, message, traceId);
                break;
        }
    }

    private (int StatusCode, object Body) CreateErrorResponse(Exception ex, string traceId)
    {
        return ex switch
        {
            ValidationException ve => (400, new ErrorResponse
            {
                Success = false,
                Error = ve.Message,
                ErrorCode = "VALIDATION_ERROR",
                Errors = ve.Errors,
                TraceId = traceId
            }),

            UnauthorizedAccessException => (401, new ErrorResponse
            {
                Success = false,
                Error = "Authentication required",
                ErrorCode = "UNAUTHORIZED",
                TraceId = traceId
            }),

            ForbiddenException fe => (403, new ErrorResponse
            {
                Success = false,
                Error = fe.Message,
                ErrorCode = "FORBIDDEN",
                TraceId = traceId
            }),

            NotFoundException nf => (404, new ErrorResponse
            {
                Success = false,
                Error = nf.Message,
                ErrorCode = "NOT_FOUND",
                TraceId = traceId
            }),

            ConflictException ce => (409, new ErrorResponse
            {
                Success = false,
                Error = ce.Message,
                ErrorCode = "CONFLICT",
                TraceId = traceId
            }),

            DomainException de => (422, new ErrorResponse
            {
                Success = false,
                Error = de.Message,
                ErrorCode = de.ErrorCode,
                TraceId = traceId
            }),

            _ => (500, new ErrorResponse
            {
                Success = false,
                Error = _environment.IsDevelopment()
                    ? ex.Message
                    : "An unexpected error occurred",
                ErrorCode = "INTERNAL_ERROR",
                TraceId = traceId,
                StackTrace = _environment.IsDevelopment() ? ex.StackTrace : null
            })
        };
    }
}

/// <summary>
/// EN: Standardized error response.
/// VI: Response lỗi chuẩn hóa.
/// </summary>
public class ErrorResponse
{
    public bool Success { get; set; }
    public string Error { get; set; } = string.Empty;
    public string ErrorCode { get; set; } = string.Empty;
    public string? TraceId { get; set; }
    public IReadOnlyDictionary<string, string[]>? Errors { get; set; }
    public string? StackTrace { get; set; }
}
```

---

## Domain Exceptions

### Complete Exception Hierarchy

```csharp
/// <summary>
/// EN: Base domain exception with error code.
/// VI: Domain exception cơ sở với error code.
/// </summary>
public abstract class DomainException : Exception
{
    public string ErrorCode { get; }

    protected DomainException(string message, string errorCode)
        : base(message)
    {
        ErrorCode = errorCode;
    }

    protected DomainException(string message, string errorCode, Exception innerException)
        : base(message, innerException)
    {
        ErrorCode = errorCode;
    }
}

/// <summary>
/// EN: Validation exception with field-level errors.
/// VI: Exception validation với lỗi từng field.
/// </summary>
public class ValidationException : DomainException
{
    public IReadOnlyDictionary<string, string[]> Errors { get; }

    public ValidationException(string message)
        : base(message, "VALIDATION_ERROR")
    {
        Errors = new Dictionary<string, string[]>();
    }

    public ValidationException(string field, string error)
        : base($"Validation failed for {field}", "VALIDATION_ERROR")
    {
        Errors = new Dictionary<string, string[]>
        {
            { field, new[] { error } }
        };
    }

    public ValidationException(IDictionary<string, string[]> errors)
        : base("One or more validation errors occurred", "VALIDATION_ERROR")
    {
        Errors = new Dictionary<string, string[]>(errors);
    }

    public ValidationException(IEnumerable<FluentValidation.Results.ValidationFailure> failures)
        : base("One or more validation errors occurred", "VALIDATION_ERROR")
    {
        Errors = failures
            .GroupBy(f => f.PropertyName)
            .ToDictionary(
                g => g.Key,
                g => g.Select(f => f.ErrorMessage).ToArray());
    }
}

/// <summary>
/// EN: Resource not found exception.
/// VI: Exception không tìm thấy resource.
/// </summary>
public class NotFoundException : DomainException
{
    public string ResourceType { get; }
    public object? ResourceId { get; }

    public NotFoundException(string resourceType, object resourceId)
        : base($"{resourceType} with ID '{resourceId}' was not found", "NOT_FOUND")
    {
        ResourceType = resourceType;
        ResourceId = resourceId;
    }

    public NotFoundException(string message)
        : base(message, "NOT_FOUND")
    {
        ResourceType = "Resource";
    }
}

/// <summary>
/// EN: Conflict exception for duplicate resources.
/// VI: Exception xung đột cho resource trùng lặp.
/// </summary>
public class ConflictException : DomainException
{
    public ConflictException(string message)
        : base(message, "CONFLICT")
    { }

    public ConflictException(string resourceType, string identifier)
        : base($"{resourceType} with identifier '{identifier}' already exists", "CONFLICT")
    { }
}

/// <summary>
/// EN: Forbidden exception for permission errors.
/// VI: Exception forbidden cho lỗi phân quyền.
/// </summary>
public class ForbiddenException : DomainException
{
    public ForbiddenException(string message = "You do not have permission to perform this action")
        : base(message, "FORBIDDEN")
    { }
}

/// <summary>
/// EN: Business rule exception.
/// VI: Exception quy tắc nghiệp vụ.
/// </summary>
public class BusinessRuleException : DomainException
{
    public BusinessRuleException(string message, string errorCode = "BUSINESS_RULE_VIOLATION")
        : base(message, errorCode)
    { }
}
```

---

## Validation with FluentValidation

### Validation Behavior for MediatR

```csharp
/// <summary>
/// EN: MediatR pipeline behavior for FluentValidation.
/// VI: MediatR pipeline behavior cho FluentValidation.
/// </summary>
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    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 Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, cancellationToken)));

        var failures = validationResults
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
            throw new ValidationException(failures);

        return await next();
    }
}

// EN: Registration in Program.cs / VI: Đăng ký trong Program.cs
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});

builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
```

### Sample Validators

```csharp
/// <summary>
/// EN: Validator for CreateOrderCommand.
/// VI: Validator cho CreateOrderCommand.
/// </summary>
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.UserId)
            .NotEmpty().WithMessage("User ID is required");

        RuleFor(x => x.ShippingAddress)
            .NotNull().WithMessage("Shipping address is required")
            .SetValidator(new AddressValidator()!);

        RuleFor(x => x.Items)
            .NotEmpty().WithMessage("At least one item is required");

        RuleForEach(x => x.Items)
            .SetValidator(new OrderItemValidator());
    }
}

public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(x => x.Street)
            .NotEmpty().WithMessage("Street is required")
            .MaximumLength(200);

        RuleFor(x => x.City)
            .NotEmpty().WithMessage("City is required")
            .MaximumLength(100);

        RuleFor(x => x.PostalCode)
            .NotEmpty().WithMessage("Postal code is required")
            .Matches(@"^\d{5}(-\d{4})?$").WithMessage("Invalid postal code format");
    }
}

public class OrderItemValidator : AbstractValidator<OrderItemDto>
{
    public OrderItemValidator()
    {
        RuleFor(x => x.ProductId)
            .NotEmpty().WithMessage("Product ID is required");

        RuleFor(x => x.Quantity)
            .GreaterThan(0).WithMessage("Quantity must be positive");

        RuleFor(x => x.UnitPrice)
            .GreaterThanOrEqualTo(0).WithMessage("Price cannot be negative");
    }
}
```

---

## Result Pattern

### Generic Result Type

```csharp
/// <summary>
/// EN: Result type for operations that can fail.
/// VI: Result type cho operations có thể thất bại.
/// </summary>
public class Result
{
    public bool IsSuccess { get; }
    public bool IsFailure => !IsSuccess;
    public string? Error { get; }
    public string? ErrorCode { get; }

    protected Result(bool isSuccess, string? error, string? errorCode)
    {
        IsSuccess = isSuccess;
        Error = error;
        ErrorCode = errorCode;
    }

    public static Result Success() => new(true, null, null);
    public static Result Failure(string error, string errorCode = "ERROR") 
        => new(false, error, errorCode);
    public static Result<T> Success<T>(T value) => new(value, true, null, null);
    public static Result<T> Failure<T>(string error, string errorCode = "ERROR") 
        => new(default!, false, error, errorCode);
}

/// <summary>
/// EN: Result type with value.
/// VI: Result type có giá trị.
/// </summary>
public class Result<T> : Result
{
    public T Value { get; }

    internal Result(T value, bool isSuccess, string? error, string? errorCode)
        : base(isSuccess, error, errorCode)
    {
        Value = value;
    }

    public static implicit operator Result<T>(T value) => Success(value);
}

/// <summary>
/// EN: Result extensions for common patterns.
/// VI: Extensions cho các mẫu phổ biến.
/// </summary>
public static class ResultExtensions
{
    public static Result<TOut> Map<TIn, TOut>(
        this Result<TIn> result, 
        Func<TIn, TOut> mapper)
    {
        return result.IsSuccess
            ? Result.Success(mapper(result.Value))
            : Result.Failure<TOut>(result.Error!, result.ErrorCode!);
    }

    public static async Task<Result<TOut>> MapAsync<TIn, TOut>(
        this Task<Result<TIn>> resultTask,
        Func<TIn, TOut> mapper)
    {
        var result = await resultTask;
        return result.Map(mapper);
    }

    public static Result<T> Ensure<T>(
        this Result<T> result,
        Func<T, bool> predicate,
        string error,
        string errorCode = "ERROR")
    {
        if (result.IsFailure)
            return result;

        return predicate(result.Value)
            ? result
            : Result.Failure<T>(error, errorCode);
    }
}
```

### Using Result Pattern

```csharp
/// <summary>
/// EN: Command handler returning Result instead of throwing.
/// VI: Command handler trả về Result thay vì throw.
/// </summary>
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, Result<OrderResult>>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IUserService _userService;

    public async Task<Result<OrderResult>> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        // EN: Validate user exists
        // VI: Xác thực user tồn tại
        var userExists = await _userService.ExistsAsync(request.UserId, ct);
        if (!userExists)
            return Result.Failure<OrderResult>("User not found", "USER_NOT_FOUND");

        // EN: Validate items
        // VI: Xác thực items
        if (!request.Items.Any())
            return Result.Failure<OrderResult>("At least one item required", "EMPTY_ORDER");

        // EN: Create order
        // VI: Tạo order
        var order = new Order(request.UserId, request.ShippingAddress);
        foreach (var item in request.Items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }

        await _orderRepository.AddAsync(order, ct);
        await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

        return Result.Success(new OrderResult(order.Id));
    }
}

// EN: Controller using Result pattern / VI: Controller dùng Result pattern
[HttpPost]
public async Task<IActionResult> CreateOrder(
    CreateOrderRequest request,
    CancellationToken ct)
{
    var command = request.ToCommand(GetUserId());
    var result = await _mediator.Send(command, ct);

    if (result.IsFailure)
    {
        return result.ErrorCode switch
        {
            "USER_NOT_FOUND" => NotFound(ApiResponse.Fail(result.Error!, result.ErrorCode)),
            "EMPTY_ORDER" => BadRequest(ApiResponse.Fail(result.Error!, result.ErrorCode)),
            _ => StatusCode(500, ApiResponse.Fail(result.Error!, result.ErrorCode))
        };
    }

    return CreatedAtAction(
        nameof(GetOrder),
        new { orderId = result.Value.OrderId },
        ApiResponse.Ok(result.Value));
}
```

---

## Polly Resiliency

### Comprehensive Resilience Configuration

```csharp
/// <summary>
/// EN: Configure resilient HTTP client with Polly.
/// VI: Cấu hình HTTP client có khả năng phục hồi với Polly.
/// </summary>

// EN: Using Microsoft.Extensions.Http.Resilience (Polly v8 integration)
// VI: Sử dụng Microsoft.Extensions.Http.Resilience (tích hợp Polly v8)
builder.Services.AddHttpClient<IExternalPaymentService, ExternalPaymentService>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["Services:Payment:BaseUrl"]!);
})
.AddResilienceHandler("payment-service", builder =>
{
    // EN: Retry policy with exponential backoff
    // VI: Retry policy với thời gian chờ tăng dần
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromMilliseconds(500),
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true, // EN: Add randomness / VI: Thêm ngẫu nhiên
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .HandleResult(r => (int)r.StatusCode >= 500)
            .Handle<HttpRequestException>()
            .Handle<TimeoutRejectedException>(),
        OnRetry = args =>
        {
            Console.WriteLine(
                $"EN: Retry {args.AttemptNumber} after {args.RetryDelay} / " +
                $"VI: Thử lại lần {args.AttemptNumber} sau {args.RetryDelay}");
            return ValueTask.CompletedTask;
        }
    });

    // EN: Circuit breaker
    // VI: Circuit breaker
    builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        FailureRatio = 0.5, // EN: 50% failures / VI: 50% thất bại
        SamplingDuration = TimeSpan.FromSeconds(30),
        MinimumThroughput = 10,
        BreakDuration = TimeSpan.FromSeconds(30),
        OnOpened = args =>
        {
            Console.WriteLine("EN: Circuit opened / VI: Circuit đã mở");
            return ValueTask.CompletedTask;
        },
        OnClosed = args =>
        {
            Console.WriteLine("EN: Circuit closed / VI: Circuit đã đóng");
            return ValueTask.CompletedTask;
        }
    });

    // EN: Timeout per request
    // VI: Timeout cho mỗi request
    builder.AddTimeout(TimeSpan.FromSeconds(10));
});
```

### Resilient Database Operations

```csharp
/// <summary>
/// EN: Configure EF Core with retry logic.
/// VI: Cấu hình EF Core với logic retry.
/// </summary>
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseNpgsql(
        builder.Configuration.GetConnectionString("DefaultConnection"),
        npgsqlOptions =>
        {
            // EN: Enable retry on transient failures
            // VI: Bật retry cho lỗi tạm thời
            npgsqlOptions.EnableRetryOnFailure(
                maxRetryCount: 3,
                maxRetryDelay: TimeSpan.FromSeconds(30),
                errorCodesToAdd: null);
            
            npgsqlOptions.CommandTimeout(30);
        });
});
```

---

## Health Checks

### Comprehensive Health Check Setup

```csharp
/// <summary>
/// EN: Configure comprehensive health checks.
/// VI: Cấu hình health checks toàn diện.
/// </summary>

// EN: Add health checks / VI: Thêm health checks
builder.Services.AddHealthChecks()
    // EN: Database / VI: Database
    .AddNpgSql(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection")!,
        name: "postgresql",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "db", "critical" })
    
    // EN: Redis cache / VI: Redis cache
    .AddRedis(
        redisConnectionString: builder.Configuration["Redis:ConnectionString"]!,
        name: "redis",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "cache" })
    
    // EN: External services / VI: Services bên ngoài
    .AddUrlGroup(
        new Uri(builder.Configuration["Services:Payment:HealthUrl"]!),
        name: "payment-service",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "external" })
    
    // EN: Custom health check / VI: Health check tùy chỉnh
    .AddCheck<StorageHealthCheck>("storage", tags: new[] { "storage", "critical" });

// EN: Configure endpoints / VI: Cấu hình endpoints
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    // EN: Liveness - just check if app is running
    // VI: Liveness - chỉ kiểm tra app có đang chạy không
    Predicate = _ => false,
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync("{\"status\":\"Healthy\"}");
    }
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    // EN: Readiness - check critical dependencies
    // VI: Readiness - kiểm tra các dependency quan trọng
    Predicate = check => check.Tags.Contains("critical"),
    ResponseWriter = WriteHealthCheckResponse
});

app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = WriteHealthCheckResponse
});

static async Task WriteHealthCheckResponse(HttpContext context, HealthReport report)
{
    context.Response.ContentType = "application/json";

    var result = new
    {
        status = report.Status.ToString(),
        totalDuration = report.TotalDuration.TotalMilliseconds,
        checks = report.Entries.Select(e => new
        {
            name = e.Key,
            status = e.Value.Status.ToString(),
            duration = e.Value.Duration.TotalMilliseconds,
            description = e.Value.Description,
            exception = e.Value.Exception?.Message
        })
    };

    await context.Response.WriteAsJsonAsync(result);
}
```

### Custom Health Check Implementation

```csharp
/// <summary>
/// EN: Custom health check for storage service.
/// VI: Health check tùy chỉnh cho storage service.
/// </summary>
public class StorageHealthCheck : IHealthCheck
{
    private readonly IMinioClient _minioClient;
    private readonly ILogger<StorageHealthCheck> _logger;

    public StorageHealthCheck(
        IMinioClient minioClient,
        ILogger<StorageHealthCheck> logger)
    {
        _minioClient = minioClient;
        _logger = logger;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // EN: Check if bucket exists and is accessible
            // VI: Kiểm tra bucket tồn tại và có thể truy cập
            var bucketExists = await _minioClient.BucketExistsAsync(
                new BucketExistsArgs().WithBucket("uploads"),
                cancellationToken);

            if (!bucketExists)
            {
                return HealthCheckResult.Degraded(
                    "Storage bucket not found",
                    data: new Dictionary<string, object>
                    {
                        { "bucket", "uploads" }
                    });
            }

            return HealthCheckResult.Healthy(
                "Storage service is healthy",
                data: new Dictionary<string, object>
                {
                    { "bucket", "uploads" },
                    { "status", "available" }
                });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "EN: Storage health check failed / VI: Health check storage thất bại");
            
            return HealthCheckResult.Unhealthy(
                "Storage service is unavailable",
                exception: ex);
        }
    }
}
```

---

## Problem Details

### RFC 7807 Problem Details

```csharp
/// <summary>
/// EN: Configure Problem Details for standardized errors.
/// VI: Cấu hình Problem Details cho lỗi chuẩn hóa.
/// </summary>

// EN: Add Problem Details / VI: Thêm Problem Details
builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = context =>
    {
        context.ProblemDetails.Extensions["traceId"] = 
            Activity.Current?.Id ?? context.HttpContext.TraceIdentifier;
        context.ProblemDetails.Extensions["timestamp"] = DateTime.UtcNow;
    };
});

// EN: Use exception handler with Problem Details
// VI: Sử dụng exception handler với Problem Details
app.UseExceptionHandler(appBuilder =>
{
    appBuilder.Run(async context =>
    {
        var exceptionHandler = context.Features.Get<IExceptionHandlerFeature>();
        var exception = exceptionHandler?.Error;

        if (exception != null)
        {
            var problemDetails = exception switch
            {
                ValidationException ve => new ProblemDetails
                {
                    Status = 400,
                    Title = "Validation Error",
                    Detail = ve.Message,
                    Extensions = { ["errors"] = ve.Errors }
                },
                NotFoundException nf => new ProblemDetails
                {
                    Status = 404,
                    Title = "Not Found",
                    Detail = nf.Message
                },
                _ => new ProblemDetails
                {
                    Status = 500,
                    Title = "Internal Server Error",
                    Detail = "An unexpected error occurred"
                }
            };

            problemDetails.Extensions["traceId"] = context.TraceIdentifier;
            context.Response.StatusCode = problemDetails.Status ?? 500;
            context.Response.ContentType = "application/problem+json";
            await context.Response.WriteAsJsonAsync(problemDetails);
        }
    });
});
```

---

## Resources / Tài Nguyên

- [Microsoft: Handle Errors in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors)
- [Polly Documentation](https://github.com/App-vNext/Polly)
- [FluentValidation](https://docs.fluentvalidation.net/)
- [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807)
- [Health Checks in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks)
