From e55a6a36631c8ac3657088fb55a43c180488015e Mon Sep 17 00:00:00 2001 From: TekH Date: Fri, 29 May 2026 00:20:55 +0200 Subject: [PATCH] Add global exception handling middleware Introduced `ExceptionHandlingMiddleware` to handle exceptions globally, log errors, and return appropriate JSON responses. Registered the middleware in `Program.cs` to ensure all requests are processed through it. Added localization support in `Program.cs` to enable localized error messages and other features. --- .../Middleware/ExceptionHandlingMiddleware.cs | 99 +++++++++++++++++++ src/DigitalData.Auth.API/Program.cs | 5 + 2 files changed, 104 insertions(+) create mode 100644 src/DigitalData.Auth.API/Middleware/ExceptionHandlingMiddleware.cs diff --git a/src/DigitalData.Auth.API/Middleware/ExceptionHandlingMiddleware.cs b/src/DigitalData.Auth.API/Middleware/ExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..d89e728 --- /dev/null +++ b/src/DigitalData.Auth.API/Middleware/ExceptionHandlingMiddleware.cs @@ -0,0 +1,99 @@ +using DigitalData.Core.Exceptions; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace DigitalData.Auth.API.Middleware; + +/// +/// Middleware for handling exceptions globally in the application. +/// Captures exceptions thrown during the request pipeline execution, +/// logs them, and returns an appropriate HTTP response with a JSON error details. +/// +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next middleware in the request pipeline. + /// The logger instance for logging exceptions. + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Invokes the middleware to handle the HTTP request. + /// + /// The HTTP context of the current request. + /// A task that represents the asynchronous operation. + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); // Continue down the pipeline + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex, _logger); + } + } + + /// + /// Handles exceptions by logging them and writing an appropriate JSON response. + /// + /// The HTTP context of the current request. + /// The exception that occurred. + /// The logger instance for logging the exception. + /// A task that represents the asynchronous operation. + private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger) + { + context.Response.ContentType = "application/json"; + + ValidationProblemDetails? details = null; + + switch (exception) + { + case BadRequestException badRequestEx: + if (badRequestEx.InnerException is not null) + { + logger.LogError( + badRequestEx.InnerException, + "BadRequestException inner exception captured."); + } + + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + details = new() + { + Title = "Bad Procedure", + Detail = badRequestEx.Message + }; + break; + + case NotFoundException notFoundEx: + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + details = new() + { + Title = "Not Found", + Detail = notFoundEx.Message + }; + break; + + default: + logger.LogError(exception, "Unhandled exception occurred."); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + details = new() + { + Title = "Internal Server Error", + Detail = "An unexpected error occurred. Please try again later." + }; + break; + } + + if (details is not null) + await context.Response.WriteAsJsonAsync(details); + } +} \ No newline at end of file diff --git a/src/DigitalData.Auth.API/Program.cs b/src/DigitalData.Auth.API/Program.cs index a9fbc07..9a50a02 100644 --- a/src/DigitalData.Auth.API/Program.cs +++ b/src/DigitalData.Auth.API/Program.cs @@ -1,6 +1,7 @@ using DigitalData.Auth.API.Config; using DigitalData.Auth.API.Entities; using DigitalData.Auth.API.Hubs; +using DigitalData.Auth.API.Middleware; using DigitalData.Auth.API.Services; using DigitalData.Core.Abstractions.Security.Extensions; using DigitalData.Core.Abstractions.Security.Services; @@ -78,6 +79,8 @@ try var cnn_str = builder.Configuration.GetConnectionString("Default") ?? throw new InvalidOperationException("Default connection string is not found."); + builder.Services.AddLocalization(); + builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); @@ -174,6 +177,8 @@ try var app = builder.Build(); + app.UseMiddleware(); + issuerSigningKeyInitiator = new Lazy(() => { var factory = app.Services.GetRequiredService();