mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
172 lines
6.3 KiB
C#
172 lines
6.3 KiB
C#
// API.Core/Filters/GlobalExceptionFilter.cs
|
|
|
|
using Domain.Exceptions;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Filters;
|
|
using Microsoft.Data.SqlClient;
|
|
using Shared.Contracts;
|
|
using ValidationException = FluentValidation.ValidationException;
|
|
|
|
namespace API.Core;
|
|
|
|
/// <summary>
|
|
/// MVC exception filter that converts unhandled exceptions raised by controller actions into
|
|
/// consistent JSON error responses with appropriate HTTP status codes.
|
|
/// </summary>
|
|
/// <param name="logger">Logger used to record unhandled exceptions before they are translated into a response.</param>
|
|
public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
|
|
: IExceptionFilter
|
|
{
|
|
/// <summary>
|
|
/// Logs the exception and sets <see cref="ExceptionContext.Result" /> to an appropriate error response,
|
|
/// marking the exception as handled.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Maps exception types to responses as follows:
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="FluentValidation.ValidationException" /> → <c>400 Bad Request</c> with per-property
|
|
/// validation error messages.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="ConflictException" /> → <c>409 Conflict</c> with a <see cref="ResponseBody" />
|
|
/// message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="NotFoundException" /> → <c>404 Not Found</c> with a <see cref="ResponseBody" />
|
|
/// message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="UnauthorizedException" /> → <c>401 Unauthorized</c> with a
|
|
/// <see cref="ResponseBody" /> message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="ForbiddenException" /> → <c>403 Forbidden</c> with a <see cref="ResponseBody" />
|
|
/// message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="SqlException" /> → <c>503 Service Unavailable</c> with a generic database error
|
|
/// message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>
|
|
/// <see cref="Domain.Exceptions.ValidationException" /> → <c>400 Bad Request</c> with a
|
|
/// <see cref="ResponseBody" /> message.
|
|
/// </description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>Any other exception → <c>500 Internal Server Error</c> with a generic error message.</description>
|
|
/// </item>
|
|
/// </list>
|
|
/// </remarks>
|
|
/// <param name="context">
|
|
/// The context for the unhandled exception, including the exception itself and the result to
|
|
/// populate.
|
|
/// </param>
|
|
public void OnException(ExceptionContext context)
|
|
{
|
|
logger.LogError(context.Exception, "Unhandled exception occurred");
|
|
|
|
switch (context.Exception)
|
|
{
|
|
case ValidationException fluentValidationException:
|
|
Dictionary<string, string[]> errors = fluentValidationException
|
|
.Errors.GroupBy(e => e.PropertyName)
|
|
.ToDictionary(
|
|
g => g.Key,
|
|
g => g.Select(e => e.ErrorMessage).ToArray()
|
|
);
|
|
|
|
context.Result = new BadRequestObjectResult(
|
|
new { message = "Validation failed", errors }
|
|
);
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case ConflictException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = ex.Message }
|
|
)
|
|
{
|
|
StatusCode = 409
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case NotFoundException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = ex.Message }
|
|
)
|
|
{
|
|
StatusCode = 404
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case UnauthorizedException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = ex.Message }
|
|
)
|
|
{
|
|
StatusCode = 401
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case ForbiddenException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = ex.Message }
|
|
)
|
|
{
|
|
StatusCode = 403
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case SqlException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = "A database error occurred." }
|
|
)
|
|
{
|
|
StatusCode = 503
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
case Domain.Exceptions.ValidationException ex:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody { Message = ex.Message }
|
|
)
|
|
{
|
|
StatusCode = 400
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
|
|
default:
|
|
context.Result = new ObjectResult(
|
|
new ResponseBody
|
|
{
|
|
Message = "An unexpected error occurred"
|
|
}
|
|
)
|
|
{
|
|
StatusCode = 500
|
|
};
|
|
context.ExceptionHandled = true;
|
|
break;
|
|
}
|
|
}
|
|
} |