Add Global Exception Handling

This commit is contained in:
lumijiez
2025-06-10 23:30:58 +03:00
parent b8ddee390a
commit db61171ada
8 changed files with 102 additions and 7 deletions

View File

@@ -0,0 +1,7 @@
namespace Imprink.Application.Exceptions;
public abstract class BaseApplicationException : Exception
{
protected BaseApplicationException(string message) : base(message) { }
protected BaseApplicationException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -0,0 +1,7 @@
namespace Imprink.Application.Exceptions;
public class NotFoundException : BaseApplicationException
{
public NotFoundException(string message) : base(message) { }
public NotFoundException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,3 +1,4 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Users.Dtos;
using Imprink.Domain.Entities.Users;
using MediatR;
@@ -10,7 +11,8 @@ public class DeleteUserRoleHandler(IUnitOfWork uw) : IRequestHandler<DeleteUserR
{
public async Task<UserRoleDto?> Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken)
{
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return null;
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
var userRole = new UserRole
{

View File

@@ -1,3 +1,4 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Users.Dtos;
using MediatR;
@@ -9,7 +10,8 @@ public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler<GetUserRolesCo
{
public async Task<IEnumerable<RoleDto>> Handle(GetUserRolesCommand request, CancellationToken cancellationToken)
{
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return [];
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);

View File

@@ -1,3 +1,4 @@
using Imprink.Application.Exceptions;
using Imprink.Application.Users.Dtos;
using Imprink.Domain.Entities.Users;
using MediatR;
@@ -10,7 +11,8 @@ public class SetUserRoleHandler(IUnitOfWork uw) : IRequestHandler<SetUserRoleCom
{
public async Task<UserRoleDto?> Handle(SetUserRoleCommand request, CancellationToken cancellationToken)
{
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return null;
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
var userRole = new UserRole
{

View File

@@ -11,7 +11,7 @@ namespace Imprink.WebApi.Controllers.Users;
public class UserRoleController(IMediator mediator) : ControllerBase
{
[Authorize]
[HttpGet("/me")]
[HttpGet("me")]
public async Task<IActionResult> GetMyRoles()
{
var claims = User.Claims as Claim[] ?? User.Claims.ToArray();
@@ -23,7 +23,7 @@ public class UserRoleController(IMediator mediator) : ControllerBase
}
[Authorize(Roles = "Admin")]
[HttpPost("/set")]
[HttpPost("set")]
public async Task<IActionResult> SetUserRole(SetUserRoleCommand command)
{
var userRole = await mediator.Send(command);
@@ -35,7 +35,7 @@ public class UserRoleController(IMediator mediator) : ControllerBase
}
[Authorize(Roles = "Admin")]
[HttpPost("/unset")]
[HttpPost("unset")]
public async Task<IActionResult> UnsetUserRole(DeleteUserRoleCommand command)
{
var userRole = await mediator.Send(command);

View File

@@ -0,0 +1,75 @@
using System.Net;
using System.Text.Json;
using Imprink.Application.Exceptions;
namespace Imprink.WebApi.Middleware;
public class ExceptionHandlingMiddleware(
RequestDelegate next,
ILogger<ExceptionHandlingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
var (_, _, shouldLog) = GetStatusCodeAndMessage(ex);
if (shouldLog)
{
logger.LogError(ex, "An unhandled exception occurred: {Message}", ex.Message);
}
else
{
logger.LogInformation("Handled: {Message}", ex.Message);
}
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
var (statusCode, message, _) = GetStatusCodeAndMessage(exception);
context.Response.StatusCode = (int)statusCode;
var response = new
{
error = new
{
message,
timestamp = DateTime.UtcNow,
}
};
var jsonResponse = JsonSerializer.Serialize(response, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
await context.Response.WriteAsync(jsonResponse);
}
private static (HttpStatusCode statusCode, string message, bool shouldLog) GetStatusCodeAndMessage(Exception exception)
{
return exception switch
{
NotFoundException => (HttpStatusCode.NotFound, exception.Message, false),
_ => (HttpStatusCode.InternalServerError, "An internal server error occurred", true)
};
}
}
public static class GlobalExceptionHandlingMiddlewareExtensions
{
public static void UseGlobalExceptionHandling(this IApplicationBuilder builder)
{
builder.UseMiddleware<ExceptionHandlingMiddleware>();
}
}

View File

@@ -140,13 +140,13 @@ public static class Startup
}
}
app.UseGlobalExceptionHandling();
app.UseRequestTiming();
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseDeveloperExceptionPage();
}
else
{