CG.Infrastructure.Exceptions 3.10.3

dotnet add package CG.Infrastructure.Exceptions --version 3.10.3
                    
NuGet\Install-Package CG.Infrastructure.Exceptions -Version 3.10.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CG.Infrastructure.Exceptions" Version="3.10.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CG.Infrastructure.Exceptions" Version="3.10.3" />
                    
Directory.Packages.props
<PackageReference Include="CG.Infrastructure.Exceptions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CG.Infrastructure.Exceptions --version 3.10.3
                    
#r "nuget: CG.Infrastructure.Exceptions, 3.10.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CG.Infrastructure.Exceptions@3.10.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CG.Infrastructure.Exceptions&version=3.10.3
                    
Install as a Cake Addin
#tool nuget:?package=CG.Infrastructure.Exceptions&version=3.10.3
                    
Install as a Cake Tool

CG.Infrastructure.Exceptions

A comprehensive .NET library providing standardized exception handling, custom exception types, and middleware for consistent error management across ASP.NET Core applications.

Overview

CG.Infrastructure.Exceptions provides a robust foundation for exception handling in .NET applications, featuring custom exception types for common scenarios and middleware for centralized error handling. This library ensures consistent error responses and proper logging while maintaining clean, maintainable code.

Features

  • Custom Exception Types: Predefined exceptions for common application scenarios
  • Exception Handling Middleware: Centralized error handling with automatic HTTP status code mapping
  • Structured Error Responses: Consistent JSON error response format
  • Automatic Logging: Built-in error logging with structured logging support
  • HTTP Status Code Mapping: Automatic mapping of exception types to appropriate HTTP status codes
  • JSON Serialization: Proper error response serialization for API consumers

Requirements

  • .NET 10.0 or later
  • ASP.NET Core 9.0 or later
  • Microsoft.Extensions.Logging

Installation

dotnet add package CG.Infrastructure.Exceptions

Quick Start

1. Register the Middleware

using Infrastructure.Exceptions.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddTransient<ExceptionHandlingMiddleware>();

var app = builder.Build();

// Use the exception handling middleware early in the pipeline
app.UseMiddleware<ExceptionHandlingMiddleware>();

// Other middleware and endpoints
app.MapControllers();
app.Run();

2. Use Custom Exceptions

using Infrastructure.Exceptions.Messages;

public class UserService
{
    public async Task<User> GetUserByIdAsync(Guid id)
    {
        var user = await _userRepository.GetByIdAsync(id);
        
        if (user == null)
        {
            throw new NotFoundException($"User with ID {id} was not found");
        }
        
        return user;
    }
    
    public async Task<User> CreateUserAsync(CreateUserRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Email))
        {
            throw new BadRequestException("Email is required");
        }
        
        if (!IsValidEmail(request.Email))
        {
            throw new BadRequestException("Invalid email format");
        }
        
        // Continue with user creation...
    }
}

Core Functionality

Custom Exception Types

NotFoundException

Abstract base class for resource not found scenarios.

public abstract class NotFoundException(string message) : Exception(message)
{
}

// Usage
throw new NotFoundException("User not found");
BadRequestException

Abstract base class for invalid request scenarios.

public abstract class BadRequestException(string message) : Exception(message)
{
}

// Usage
throw new BadRequestException("Invalid input data");
ConfigurationValidationException

For configuration validation failures.

public class ConfigurationValidationException(string message) : Exception(message)
{
}

// Usage
if (string.IsNullOrEmpty(connectionString))
{
    throw new ConfigurationValidationException("Database connection string is required");
}
CatchFailedCallException

For failed API or service calls.

public sealed class CatchFailedCallException(string message) : BadRequestException($"The call failed with the following: '{message}'")
{
}

// Usage
try
{
    var result = await _externalService.CallAsync();
}
catch (Exception ex)
{
    throw new CatchFailedCallException(ex.Message);
}

Exception Handling Middleware

The ExceptionHandlingMiddleware provides centralized exception handling with automatic HTTP status code mapping and structured error responses.

Automatic Status Code Mapping
context.Response.StatusCode = exception switch
{
    JsonException => StatusCodes.Status400BadRequest,
    BadRequestException => StatusCodes.Status400BadRequest,
    NotFoundException => StatusCodes.Status404NotFound,
    _ => StatusCodes.Status500InternalServerError
};
Structured Error Responses

The middleware automatically maps exceptions to appropriate error messages:

  • JsonException: Returns "Invalid JSON format" for system-level JSON parsing errors
  • All other exceptions: Return the actual exception message for custom business logic errors
{
  "error": "User with ID 123 was not found"
}
Logging Integration
logger.LogError(ex, "Failed to execute the context: '{message}'", ex.Message);

Usage Examples

1. Controller Exception Handling

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;
    
    public UsersController(IUserService userService)
    {
        _userService = userService;
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(Guid id)
    {
        try
        {
            var user = await _userService.GetUserByIdAsync(id);
            return Ok(user);
        }
        catch (NotFoundException ex)
        {
            // The middleware will handle this automatically
            throw;
        }
    }
    
    [HttpPost]
    public async Task<ActionResult<User>> CreateUser([FromBody] CreateUserRequest request)
    {
        try
        {
            var user = await _userService.CreateUserAsync(request);
            return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
        }
        catch (BadRequestException ex)
        {
            // The middleware will handle this automatically
            throw;
        }
    }
}

2. Service Layer Exception Handling

public class ProductService
{
    private readonly IProductRepository _productRepository;
    private readonly IConfiguration _configuration;
    
    public ProductService(IProductRepository productRepository, IConfiguration configuration)
    {
        _productRepository = productRepository;
        _configuration = configuration;
    }
    
    public async Task<Product> GetProductAsync(int productId)
    {
        // Validate configuration
        var maxRetries = _configuration.GetValue<int>("MaxRetries");
        if (maxRetries <= 0)
        {
            throw new ConfigurationValidationException("MaxRetries must be greater than 0");
        }
        
        // Get product
        var product = await _productRepository.GetByIdAsync(productId);
        if (product == null)
        {
            throw new NotFoundException($"Product with ID {productId} was not found");
        }
        
        return product;
    }
    
    public async Task<Product> UpdateProductAsync(int productId, UpdateProductRequest request)
    {
        // Validate input
        if (request.Price < 0)
        {
            throw new BadRequestException("Product price cannot be negative");
        }
        
        if (string.IsNullOrWhiteSpace(request.Name))
        {
            throw new BadRequestException("Product name is required");
        }
        
        // Get existing product
        var product = await GetProductAsync(productId);
        
        // Update product
        product.Update(request.Name, request.Price, request.Description);
        
        await _productRepository.UpdateAsync(product);
        return product;
    }
}

3. Repository Layer Exception Handling

public class UserRepository : IUserRepository
{
    private readonly ApplicationDbContext _context;
    
    public UserRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public async Task<User?> GetByIdAsync(Guid id)
    {
        try
        {
            return await _context.Users
                .FirstOrDefaultAsync(u => u.Id == id);
        }
        catch (Exception ex)
        {
            // Log the database error but don't expose internal details
            throw new CatchFailedCallException($"Failed to retrieve user: {ex.Message}");
        }
    }
    
    public async Task<User> AddAsync(User user)
    {
        try
        {
            var result = await _context.Users.AddAsync(user);
            await _context.SaveChangesAsync();
            return result.Entity;
        }
        catch (DbUpdateException ex)
        {
            throw new CatchFailedCallException($"Failed to create user: {ex.Message}");
        }
    }
}

Configuration

Middleware Registration Options

Option 1: Direct Registration
builder.Services.AddTransient<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Create an extension method for cleaner registration
public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseExceptionHandling(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ExceptionHandlingMiddleware>();
    }
}

// Usage
app.UseExceptionHandling();

Customizing Error Responses

You can extend the middleware to provide more detailed error responses:

public class CustomExceptionHandlingMiddleware : IMiddleware
{
    private readonly ILogger<CustomExceptionHandlingMiddleware> _logger;
    private readonly IHostEnvironment _environment;
    
    public CustomExceptionHandlingMiddleware(
        ILogger<CustomExceptionHandlingMiddleware> logger,
        IHostEnvironment environment)
    {
        _logger = logger;
        _environment = environment;
    }
    
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            await HandleExceptionAsync(context, ex);
        }
    }
    
    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        
        var (statusCode, errorMessage) = GetErrorDetails(exception);
        context.Response.StatusCode = statusCode;
        
        var response = new
        {
            error = errorMessage,
            timestamp = DateTime.UtcNow,
            traceId = context.TraceIdentifier,
            details = _environment.IsDevelopment() ? exception.ToString() : null
        };
        
        await context.Response.WriteAsync(JsonSerializer.Serialize(response));
    }
    
    private static (int statusCode, string message) GetErrorDetails(Exception exception)
    {
        return exception switch
        {
            NotFoundException => (StatusCodes.Status404NotFound, exception.Message),
            BadRequestException => (StatusCodes.Status400BadRequest, exception.Message),
            ConfigurationValidationException => (StatusCodes.Status500InternalServerError, exception.Message),
            _ => (StatusCodes.Status500InternalServerError, "An unexpected error occurred")
        };
    }
}

Best Practices

1. Exception Hierarchy

Create specific exception types for your domain:

public class UserNotFoundException : NotFoundException
{
    public UserNotFoundException(Guid userId) 
        : base($"User with ID {userId} was not found")
    {
        UserId = userId;
    }
    
    public Guid UserId { get; }
}

public class InvalidEmailException : BadRequestException
{
    public InvalidEmailException(string email) 
        : base($"'{email}' is not a valid email address")
    {
        Email = email;
    }
    
    public string Email { get; }
}

2. Middleware Order

Register the exception handling middleware early in the pipeline:

var app = builder.Build();

// Exception handling should be one of the first middleware
app.UseMiddleware<ExceptionHandlingMiddleware>();

// Other middleware
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

3. Logging Strategy

Use structured logging for better error tracking:

public class UserService
{
    private readonly ILogger<UserService> _logger;
    
    public async Task<User> GetUserAsync(Guid id)
    {
        try
        {
            var user = await _userRepository.GetByIdAsync(id);
            if (user == null)
            {
                _logger.LogWarning("User not found. UserId: {UserId}", id);
                throw new UserNotFoundException(id);
            }
            
            return user;
        }
        catch (Exception ex) when (ex is not UserNotFoundException)
        {
            _logger.LogError(ex, "Failed to retrieve user. UserId: {UserId}", id);
            throw;
        }
    }
}

4. Error Response Consistency

Maintain consistent error response formats across your API:

public class ErrorResponse
{
    public string Error { get; set; } = string.Empty;
    public string? Details { get; set; }
    public DateTime Timestamp { get; set; }
    public string TraceId { get; set; } = string.Empty;
    public string? Code { get; set; }
}

Integration Examples

1. ASP.NET Core Minimal APIs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<ExceptionHandlingMiddleware>();

var app = builder.Build();
app.UseMiddleware<ExceptionHandlingMiddleware>();

app.MapGet("/users/{id}", async (Guid id, IUserService userService) =>
{
    var user = await userService.GetUserByIdAsync(id);
    return Results.Ok(user);
});

app.Run();

2. Blazor Server

public class BlazorExceptionHandler : IErrorBoundaryLogger
{
    private readonly ILogger<BlazorExceptionHandler> _logger;
    
    public BlazorExceptionHandler(ILogger<BlazorExceptionHandler> logger)
    {
        _logger = logger;
    }
    
    public ValueTask LogErrorAsync(Exception exception)
    {
        _logger.LogError(exception, "Blazor error boundary caught an exception");
        return ValueTask.CompletedTask;
    }
}

// In Program.cs
builder.Services.AddScoped<IErrorBoundaryLogger, BlazorExceptionHandler>();

3. SignalR Hub

public class ChatHub : Hub
{
    private readonly IUserService _userService;
    
    public ChatHub(IUserService userService)
    {
        _userService = userService;
    }
    
    public async Task SendMessage(string message)
    {
        try
        {
            var user = await _userService.GetCurrentUserAsync();
            await Clients.All.SendAsync("ReceiveMessage", user.Name, message);
        }
        catch (NotFoundException ex)
        {
            await Clients.Caller.SendAsync("Error", ex.Message);
        }
    }
}

Performance Considerations

1. Middleware Performance

The exception handling middleware is lightweight and only executes when exceptions occur:

  • Minimal overhead for successful requests
  • Efficient exception type checking using pattern matching
  • Async/await for non-blocking error handling

2. Logging Performance

  • Structured logging for efficient log processing
  • Exception details are only serialized when needed
  • JSON serialization is optimized for error responses

3. Memory Management

  • Exception objects are properly disposed
  • No memory leaks from middleware registration
  • Efficient string handling in error messages

Dependencies

  • Microsoft.Extensions.Logging: For structured logging support
  • Microsoft.AspNetCore.Http: For HTTP context and middleware support
  • System.Text.Json: For JSON serialization of error responses

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Version History

  • 3.10.2: Current release with exception types and middleware
  • 3.10.1: Initial release with basic exception handling
  • 3.10.0: Foundation release

Support

For questions, issues, or contributions, please visit our GitHub repository or contact the development team.

  • CG.Infrastructure.Core - Core infrastructure services
  • CG.Infrastructure.Configuration - Configuration management
  • CG.Infrastructure.Logging - Logging infrastructure
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.10.3 155 4/1/2026
3.10.2 270 8/10/2025
3.10.1 218 7/11/2025
3.10.0 267 6/16/2025
3.9.0 235 12/10/2024
3.0.0 260 8/13/2024