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
<PackageReference Include="CG.Infrastructure.Exceptions" Version="3.10.3" />
<PackageVersion Include="CG.Infrastructure.Exceptions" Version="3.10.3" />
<PackageReference Include="CG.Infrastructure.Exceptions" />
paket add CG.Infrastructure.Exceptions --version 3.10.3
#r "nuget: CG.Infrastructure.Exceptions, 3.10.3"
#:package CG.Infrastructure.Exceptions@3.10.3
#addin nuget:?package=CG.Infrastructure.Exceptions&version=3.10.3
#tool nuget:?package=CG.Infrastructure.Exceptions&version=3.10.3
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>();
Option 2: Extension Method (Recommended)
// 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
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- 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.
Related Packages
CG.Infrastructure.Core- Core infrastructure servicesCG.Infrastructure.Configuration- Configuration managementCG.Infrastructure.Logging- Logging infrastructure
| Product | Versions 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. |
-
net10.0
- Microsoft.AspNetCore.Http.Abstractions (>= 2.3.9)
- Microsoft.Extensions.Logging (>= 10.0.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.