Skip to content

Commit

Permalink
Creating GlobalExceptionHandler for handling errors throughout projec…
Browse files Browse the repository at this point in the history
…t with custom Exception
  • Loading branch information
Edrisym committed Dec 6, 2024
1 parent 01bfb1c commit 2b7b0e5
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 4 deletions.
9 changes: 9 additions & 0 deletions EWallet.Api/EWallet.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Figgle" Version="0.5.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
Expand All @@ -29,4 +30,12 @@
</Content>
</ItemGroup>

<ItemGroup>
<Folder Include="Users\EndPoints\" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EWallet.Infrastructure\EWallet.Infrastructure.csproj" />
</ItemGroup>

</Project>
13 changes: 10 additions & 3 deletions EWallet.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
using System.Security.Authentication;
using EWallet.Infrastructure.ExceptionHandler;
using Figgle;

var builder = WebApplication.CreateBuilder(args);

Console.WriteLine(FiggleFonts.Standard.Render("Wallet"));

builder.Services
.AddCustomAuthentication(builder.Configuration)
.AddCustomAuthorization();
.AddCustomAuthorization()
.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();

builder.AddWalletDbContext();

var app = builder.Build();

app.UseExceptionHandler();

app.UseAuthentication();
app.UseAuthorization();


app.MapWalletsEndpoints();
app.MapCurrencyEndpoints();

Expand Down
1 change: 0 additions & 1 deletion EWallet.Api/UsingGlobals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#region EWallet

global using EWallet.Api.Common.Extensions;
global using EWallet.Api.Common;
global using EWallet.Api.Common.Exceptions;
global using EWallet.Api.Common.Models;
global using DefaultColumnType = EWallet.Api.Common.StaticData.WalletDbContextDefaultColumnType;
Expand Down
6 changes: 6 additions & 0 deletions EWallet.Infrastructure/ExceptionHandler/AppException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace EWallet.Infrastructure.ExceptionHandler;

public class AppException : Exception
{
// TODO : complete
}
36 changes: 36 additions & 0 deletions EWallet.Infrastructure/ExceptionHandler/ExceptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace EWallet.Infrastructure.ExceptionHandler;

public static class ExceptionExtensions
{
private const string ErrorCodeKey = "ErrorCode";

/// <summary>
/// Adds or updates the error code for the exception.
/// </summary>
/// <param name="exception">The exception to enrich.</param>
/// <param name="errorCode">The error code to add. Defaults to "GenericError".</param>
public static void AddErrorCode(this Exception exception, string errorCode = "GenericError")
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception), "Exception cannot be null.");
}

exception.Data[ErrorCodeKey] = errorCode;
}

/// <summary>
/// Retrieves the error code from the exception, if it exists.
/// </summary>
/// <param name="exception">The exception to retrieve the error code from.</param>
/// <returns>The error code, or null if none exists.</returns>
public static string? GetErrorCode(this Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception), "Exception cannot be null.");
}

return exception.Data.Contains(ErrorCodeKey) ? exception.Data[ErrorCodeKey]?.ToString() : null;
}
}
86 changes: 86 additions & 0 deletions EWallet.Infrastructure/ExceptionHandler/GlobalExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace EWallet.Infrastructure.ExceptionHandler;

public class GlobalExceptionHandler(IHostEnvironment env, ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
private const string UnhandledExceptionMsg = "An unhandled exception has occurred while executing the request.";

private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
{
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};


public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception,
CancellationToken cancellationToken)
{
exception.AddErrorCode();
logger.LogError(exception, exception is AppException ? exception.Message : UnhandledExceptionMsg);

var problemDetails = CreateProblemDetails(context, exception);
var json = ToJson(problemDetails);

const string contentType = "application/problem+json";
context.Response.ContentType = contentType;
await context.Response.WriteAsync(json, cancellationToken);

return true;
}

private ProblemDetails CreateProblemDetails(in HttpContext context, in Exception exception)
{
var errorCode = exception.GetErrorCode();
var statusCode = context.Response.StatusCode;
var reasonPhrase = ReasonPhrases.GetReasonPhrase(statusCode);
if (string.IsNullOrEmpty(reasonPhrase))
{
reasonPhrase = UnhandledExceptionMsg;
}

var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = reasonPhrase,
Extensions =
{
[nameof(errorCode)] = errorCode
}
};

if (!env.IsDevelopment())
{
return problemDetails;
}

problemDetails.Detail = exception.ToString();
problemDetails.Extensions["traceId"] = Activity.Current?.Id;
problemDetails.Extensions["requestId"] = context.TraceIdentifier;
problemDetails.Extensions["data"] = exception.Data;

return problemDetails;
}

private string ToJson(in ProblemDetails problemDetails)
{
try
{
return JsonSerializer.Serialize(problemDetails, SerializerOptions);
}
catch (Exception ex)
{
const string msg = "An exception has occurred while serializing error to JSON";
logger.LogError(ex, msg);
}

return string.Empty;
}
}
7 changes: 7 additions & 0 deletions Wallet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EWallet.Api", "EWallet.Api\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JwtAuth.Api", "JwtAuth.Api\JwtAuth.Api.csproj", "{1AD22D7C-F858-470C-819C-49ADF31A0F81}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EWallet.Infrastructure", "EWallet.Infrastructure\EWallet.Infrastructure.csproj", "{11280331-D7E1-4855-B31C-EC52A73DAEBA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -28,10 +30,15 @@ Global
{1AD22D7C-F858-470C-819C-49ADF31A0F81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD22D7C-F858-470C-819C-49ADF31A0F81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD22D7C-F858-470C-819C-49ADF31A0F81}.Release|Any CPU.Build.0 = Release|Any CPU
{11280331-D7E1-4855-B31C-EC52A73DAEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11280331-D7E1-4855-B31C-EC52A73DAEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11280331-D7E1-4855-B31C-EC52A73DAEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11280331-D7E1-4855-B31C-EC52A73DAEBA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{628581B3-CBF4-47B5-A7BE-D81634999389} = {9B06A700-D91F-43C8-977D-CCA3FBFE244A}
{0D30908F-DE5A-4896-B97A-0F1A61D958AA} = {272CA49E-0294-4E32-A94C-635F32D0B74A}
{1AD22D7C-F858-470C-819C-49ADF31A0F81} = {272CA49E-0294-4E32-A94C-635F32D0B74A}
{11280331-D7E1-4855-B31C-EC52A73DAEBA} = {272CA49E-0294-4E32-A94C-635F32D0B74A}
EndGlobalSection
EndGlobal

0 comments on commit 2b7b0e5

Please sign in to comment.