diff --git a/CR.Exceptions.AspNet.UnitTests/ExceptionMappingOptionsTests.cs b/CR.Exceptions.AspNet.UnitTests/ExceptionMappingOptionsTests.cs
index aab85e9..e526bb5 100644
--- a/CR.Exceptions.AspNet.UnitTests/ExceptionMappingOptionsTests.cs
+++ b/CR.Exceptions.AspNet.UnitTests/ExceptionMappingOptionsTests.cs
@@ -1,4 +1,5 @@
-using FluentAssertions;
+using CR.Exceptions.AspNet.Options;
+using FluentAssertions;
using Microsoft.AspNetCore.Http;
namespace CR.Exceptions.AspNet.UnitTests;
diff --git a/CR.Exceptions.AspNet/CR.Exceptions.AspNet.csproj b/CR.Exceptions.AspNet/CR.Exceptions.AspNet.csproj
index df8c212..0ed816f 100644
--- a/CR.Exceptions.AspNet/CR.Exceptions.AspNet.csproj
+++ b/CR.Exceptions.AspNet/CR.Exceptions.AspNet.csproj
@@ -7,7 +7,8 @@
- ASP.NET Core integration for CR.Exceptions.
+ ASP.NET Core integration for CR.Exceptions.
+ $(NuGetPackagePrefix).Exceptions.AspNet
diff --git a/CR.Exceptions.AspNet/CrExceptionHandler.Logger.cs b/CR.Exceptions.AspNet/CrExceptionHandler.Logger.cs
index 4ac5efc..82031b8 100644
--- a/CR.Exceptions.AspNet/CrExceptionHandler.Logger.cs
+++ b/CR.Exceptions.AspNet/CrExceptionHandler.Logger.cs
@@ -12,21 +12,21 @@ public sealed partial class CrExceptionHandler
[LoggerMessage(
Level = LogLevel.Warning,
Message = "No HTTP status code mapping found for exception type '{ExceptionType}'. Using 500 Internal Server Error.")]
- private static partial void LogMissingHttpStatusMapping(ILogger logger, Exception exception, string? exceptionType);
+ private static partial void LogMissingHttpStatusMapping(ILogger logger, Exception exception, string exceptionType);
[LoggerMessage(
Level = LogLevel.Debug,
Message = "Application exception of type '{ExceptionType}' occurred.")]
- private static partial void LogApplicationException(ILogger logger, Exception exception, string? exceptionType);
+ private static partial void LogApplicationException(ILogger logger, Exception exception, string exceptionType);
[LoggerMessage(
Level = LogLevel.Error,
Message = "An unexpected exception of type '{ExceptionType}' occurred.")]
- private static partial void LogUnhandledException(ILogger logger, Exception exception, string? exceptionType);
+ private static partial void LogUnhandledException(ILogger logger, Exception exception, string exceptionType);
[LoggerMessage(
Level = LogLevel.Warning,
- Message = "ProblemDetails extension key '{Key}' already exists. The value was overwritten.")]
+ Message = "The ProblemDetails extension '{Key}' was overwritten while building the error response.")]
private static partial void LogProblemDetailsExtensionOverwritten(ILogger logger, string key);
[LoggerMessage(
diff --git a/CR.Exceptions.AspNet/CrExceptionHandler.cs b/CR.Exceptions.AspNet/CrExceptionHandler.cs
index b4c48e1..f6f9afb 100644
--- a/CR.Exceptions.AspNet/CrExceptionHandler.cs
+++ b/CR.Exceptions.AspNet/CrExceptionHandler.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Diagnostics;
+using CR.Exceptions.AspNet.Options;
+using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
@@ -10,18 +11,18 @@ namespace CR.Exceptions.AspNet;
public sealed partial class CrExceptionHandler : IExceptionHandler
{
- private static readonly CrError[] FallbackInternalErrors =
+ private static readonly CrError[] DefaultInternalErrors =
[
new(ErrorCodes.InternalError, "An unexpected internal error occurred.")
];
private readonly IProblemDetailsService _problemDetailsService;
- private readonly ExceptionMappingOptions _options;
+ private readonly CrExceptionOptions _options;
private readonly ILogger _logger;
public CrExceptionHandler(
IProblemDetailsService problemDetailsService,
- IOptions options,
+ IOptions options,
ILogger logger)
{
_problemDetailsService = problemDetailsService;
@@ -38,7 +39,8 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e
}
var httpStatusCode = StatusCodes.Status500InternalServerError;
- var exceptionTypeFullName = exception.GetType().FullName;
+ var exceptionType = exception.GetType();
+ var exceptionTypeName = exceptionType.FullName ?? exceptionType.Name;
CrError[] errors;
string detail;
@@ -48,25 +50,25 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e
detail = crException.Message;
errors = crException.Errors;
- var statusCode = _options.FindHttpStatusCode(crException);
+ var statusCode = _options.ExceptionMapping.FindHttpStatusCode(crException);
if (statusCode is null)
{
- LogMissingHttpStatusMapping(_logger, exception, exceptionTypeFullName);
+ LogMissingHttpStatusMapping(_logger, exception, exceptionTypeName);
}
else
{
httpStatusCode = statusCode.Value;
}
- LogApplicationException(_logger, exception, exceptionTypeFullName);
+ LogApplicationException(_logger, exception, exceptionTypeName);
}
else
{
detail = "An unexpected error occurred.";
- errors = FallbackInternalErrors;
+ errors = DefaultInternalErrors;
- LogUnhandledException(_logger, exception, exceptionTypeFullName);
+ LogUnhandledException(_logger, exception, exceptionTypeName);
}
var traceId = Activity.Current?.TraceId.ToHexString() ?? httpContext.TraceIdentifier;
@@ -77,7 +79,7 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e
Exception = exception,
ProblemDetails =
{
- Type = "about:blank",
+ Type = _options.ProblemDetails.Type,
Status = httpStatusCode,
Title = string.IsNullOrWhiteSpace(title) ? "An error occurred" : title,
Detail = detail,
@@ -104,8 +106,8 @@ private void AddProblemDetailsExtension(ProblemDetails problemDetails, string ke
{
if (!problemDetails.Extensions.TryAdd(key, value))
{
- LogProblemDetailsExtensionOverwritten(_logger, key);
problemDetails.Extensions[key] = value;
+ LogProblemDetailsExtensionOverwritten(_logger, key);
}
}
}
\ No newline at end of file
diff --git a/CR.Exceptions.AspNet/ExceptionMappingOptionsExtensions.cs b/CR.Exceptions.AspNet/ExceptionMappingOptionsExtensions.cs
index 904bc5c..6d1d86c 100644
--- a/CR.Exceptions.AspNet/ExceptionMappingOptionsExtensions.cs
+++ b/CR.Exceptions.AspNet/ExceptionMappingOptionsExtensions.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Http;
+using CR.Exceptions.AspNet.Options;
+using Microsoft.AspNetCore.Http;
namespace CR.Exceptions.AspNet;
diff --git a/CR.Exceptions.AspNet/Options/CrExceptionOptions.cs b/CR.Exceptions.AspNet/Options/CrExceptionOptions.cs
new file mode 100644
index 0000000..1780a73
--- /dev/null
+++ b/CR.Exceptions.AspNet/Options/CrExceptionOptions.cs
@@ -0,0 +1,7 @@
+namespace CR.Exceptions.AspNet.Options;
+
+public sealed class CrExceptionOptions
+{
+ public ExceptionMappingOptions ExceptionMapping { get; init; } = new();
+ public ProblemDetailsOptions ProblemDetails { get; init; } = new();
+}
\ No newline at end of file
diff --git a/CR.Exceptions.AspNet/ExceptionMappingOptions.cs b/CR.Exceptions.AspNet/Options/ExceptionMappingOptions.cs
similarity index 95%
rename from CR.Exceptions.AspNet/ExceptionMappingOptions.cs
rename to CR.Exceptions.AspNet/Options/ExceptionMappingOptions.cs
index 999c598..f04cd60 100644
--- a/CR.Exceptions.AspNet/ExceptionMappingOptions.cs
+++ b/CR.Exceptions.AspNet/Options/ExceptionMappingOptions.cs
@@ -1,4 +1,4 @@
-namespace CR.Exceptions.AspNet;
+namespace CR.Exceptions.AspNet.Options;
public sealed class ExceptionMappingOptions
{
diff --git a/CR.Exceptions.AspNet/Options/ProblemDetailsOptions.cs b/CR.Exceptions.AspNet/Options/ProblemDetailsOptions.cs
new file mode 100644
index 0000000..3ee4c6f
--- /dev/null
+++ b/CR.Exceptions.AspNet/Options/ProblemDetailsOptions.cs
@@ -0,0 +1,6 @@
+namespace CR.Exceptions.AspNet.Options;
+
+public sealed class ProblemDetailsOptions
+{
+ public string Type { get; set; } = "about:blank";
+}
\ No newline at end of file
diff --git a/CR.Exceptions.AspNet/ServiceCollectionExtensions.cs b/CR.Exceptions.AspNet/ServiceCollectionExtensions.cs
index 2e8bf97..e3719c4 100644
--- a/CR.Exceptions.AspNet/ServiceCollectionExtensions.cs
+++ b/CR.Exceptions.AspNet/ServiceCollectionExtensions.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using CR.Exceptions.AspNet.Options;
+using Microsoft.Extensions.DependencyInjection;
namespace CR.Exceptions.AspNet;
@@ -10,11 +11,11 @@ public IServiceCollection AddCrExceptionHandler()
{
return services.AddCrExceptionHandler(options =>
{
- options.AddDefaultMappings();
+ options.ExceptionMapping.AddDefaultMappings();
});
}
- public IServiceCollection AddCrExceptionHandler(Action setupAction)
+ public IServiceCollection AddCrExceptionHandler(Action setupAction)
{
ArgumentNullException.ThrowIfNull(setupAction);
diff --git a/CR.Exceptions/CR.Exceptions.csproj b/CR.Exceptions/CR.Exceptions.csproj
index 5e36cca..7bf1a01 100644
--- a/CR.Exceptions/CR.Exceptions.csproj
+++ b/CR.Exceptions/CR.Exceptions.csproj
@@ -7,7 +7,8 @@
- Common exception abstractions and error models for .NET applications.
+ Common exception abstractions and error models for .NET applications.
+ $(NuGetPackagePrefix).Exceptions
diff --git a/Directory.Build.props b/Directory.Build.props
index a39f15e..f931d6f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,6 +12,8 @@
README.md
Apache-2.0
+
+ CrCore
diff --git a/README.md b/README.md
index 401733d..cd8e9a6 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,8 @@ Or configure custom exception mappings.
```csharp
builder.Services.AddCrExceptionHandler(options =>
{
- options.AddDefaultMappings();
- options.Map(499);
+ options.ExceptionMapping.AddDefaultMappings();
+ options.ExceptionMapping.Map(499);
});
```
@@ -35,21 +35,14 @@ Example:
```csharp
public sealed class UserNotFoundException : NotFoundException
{
- public UserNotFoundException(Guid userId)
- : base(
- [
- new CrError(
- "UserNotFound",
- $"User '{userId}' was not found.")
- ],
+ public UserNotFoundException(Guid userId) : base(
+ [new CrError("UserNotFound", $"User '{userId}' was not found.")],
"User was not found.")
{
}
}
```
-Each `CrException` contains one or more `CrError` objects.
-
```csharp
public sealed record class CrError(string Code, string Message);
```