Skip to content

Commit

Permalink
feat: implement global exception handler
Browse files Browse the repository at this point in the history
  • Loading branch information
undrcrxwn committed Mar 22, 2024
1 parent 9560292 commit 433d979
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 76 deletions.
7 changes: 4 additions & 3 deletions src/CrowdParlay.Social.Api/Extensions/ConfigureServices.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using CrowdParlay.Communication;
using CrowdParlay.Social.Api.Consumers;
using CrowdParlay.Social.Api.Middlewares;
using CrowdParlay.Social.Api.Services;
using MassTransit;

namespace CrowdParlay.Social.Api.Extensions;
Expand All @@ -10,10 +10,11 @@ public static class ConfigureApiExtensions
public static IServiceCollection AddApi(this IServiceCollection services, IConfiguration configuration)
{
services
.AddExceptionHandler<GlobalExceptionHandler>()
.AddProblemDetails()
.ConfigureEndpoints()
.ConfigureAuthentication()
.AddAuthorization()
.AddTransient<ExceptionHandlingMiddleware>();
.AddAuthorization();

return services.AddMassTransit(bus =>
{
Expand Down

This file was deleted.

74 changes: 74 additions & 0 deletions src/CrowdParlay.Social.Api/Services/GlobalExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Net;
using System.Net.Mime;
using CrowdParlay.Social.Application.Exceptions;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

namespace CrowdParlay.Social.Api.Services;

/// <summary>
/// Handles exceptions thrown in the ASP.NET Core pipeline and writes the details to the HTTP response in accordance with RFC 7807.
/// Gets called by the default ASP.NET Core exception handling middleware.
/// </summary>
public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
{
logger.LogError(exception, "{ExceptionMessage}", exception.Message);

var problemDetails = exception switch
{
ValidationException e => ConvertToProblemDetails(e),
FluentValidation.ValidationException e => ConvertToProblemDetails(e),
NotFoundException => GetNotFoundProblemDetails(),
ForbiddenException => GetForbiddenProblemDetails(),
_ => GetDefaultProblemDetails()
};

context.Response.ContentType = MediaTypeNames.Application.ProblemJson;
context.Response.StatusCode = problemDetails.Status ?? StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(problemDetails, GlobalSerializerOptions.SnakeCase, cancellationToken);

return true;
}

private static ProblemDetails GetDefaultProblemDetails() => new()
{
Status = (int)HttpStatusCode.InternalServerError,
Detail = "Something went wrong. Try again later.",
};

private static ValidationProblemDetails ConvertToProblemDetails(ValidationException exception) => new()
{
Status = (int)HttpStatusCode.BadRequest,
Detail = "The specified data is invalid.",
Errors = exception.Errors.ToDictionary(
x => x.Key,
x => x.Value.ToArray())
};

private static ValidationProblemDetails ConvertToProblemDetails(FluentValidation.ValidationException exception) => new()
{
Status = (int)HttpStatusCode.BadRequest,
Detail = "The specified data is invalid.",
Errors = exception.Errors
.GroupBy(failure => failure.PropertyName)
.ToDictionary(
propertyFailureGroup => propertyFailureGroup.Key,
propertyFailureGroup => propertyFailureGroup
.Select(failure => failure.ErrorMessage)
.ToArray())
};

private static ProblemDetails GetNotFoundProblemDetails() => new()
{
Status = (int)HttpStatusCode.NotFound,
Detail = "The requested resource doesn't exist."
};

private static ProblemDetails GetForbiddenProblemDetails() => new()
{
Status = (int)HttpStatusCode.Forbidden,
Detail = "You have no permission for this action."
};
}
3 changes: 1 addition & 2 deletions src/CrowdParlay.Social.Api/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using CrowdParlay.Social.Api.Extensions;
using CrowdParlay.Social.Api.Hubs;
using CrowdParlay.Social.Api.Middlewares;
using CrowdParlay.Social.Application;
using CrowdParlay.Social.Infrastructure.Persistence;
using Microsoft.AspNetCore.Http.Connections;
Expand All @@ -16,7 +15,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
.ReadFrom.Configuration(configuration)
.CreateLogger();

app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseExceptionHandler();
app.UseSerilogRequestLogging();
app.UseHealthChecks("/healthz");

Expand Down

0 comments on commit 433d979

Please sign in to comment.