diff --git a/DigitalData.Core.API/DIExtensions.cs b/DigitalData.Core.API/DIExtensions.cs
index a1145b1..07e2230 100644
--- a/DigitalData.Core.API/DIExtensions.cs
+++ b/DigitalData.Core.API/DIExtensions.cs
@@ -1,53 +1,50 @@
-using Microsoft.AspNetCore.Builder;
-using System.Configuration;
+namespace DigitalData.Core.API;
-namespace DigitalData.Core.API
+///
+/// Provides extension methods for adding middleware to the application's request pipeline.
+///
+public static class DIExtensions
{
///
- /// Provides extension methods for adding middleware to the application's request pipeline.
+ /// Adds the to the application's request pipeline to include
+ /// Content Security Policy (CSP) headers in the HTTP response.
///
- public static class DIExtensions
- {
- ///
- /// Adds the to the application's request pipeline to include
- /// Content Security Policy (CSP) headers in the HTTP response.
- ///
- /// The application builder.
- ///
- /// The CSP policy string with placeholders. The first format parameter {0} will be replaced
- /// by the nonce value.
- ///
- /// The application builder with the CSP middleware added.
- public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
- => app.UseMiddleware(policy);
+ /// The application builder.
+ ///
+ /// The CSP policy string with placeholders. The first format parameter {0} will be replaced
+ /// by the nonce value.
+ ///
+ /// The application builder with the CSP middleware added.
+ public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
+ => app.UseMiddleware(policy);
- ///
- /// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
- ///
- /// The WebApplicationBuilder instance.
- /// True if DiP mode is enabled; otherwise, false.
- public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue("DiPMode");
+ ///
+ /// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
+ ///
+ /// The WebApplicationBuilder instance.
+ /// True if DiP mode is enabled; otherwise, false.
+ public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue("DiPMode");
- ///
- /// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
- ///
- /// The WebApplication instance.
- /// True if DiP mode is enabled; otherwise, false.
- public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue("DiPMode");
+ ///
+ /// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
+ ///
+ /// The WebApplication instance.
+ /// True if DiP mode is enabled; otherwise, false.
+ public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue("DiPMode");
- ///
- /// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
- ///
- /// The WebApplicationBuilder instance.
- /// True if the environment is Development or DiP mode is enabled; otherwise, false.
- public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
+ ///
+ /// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
+ ///
+ /// The WebApplicationBuilder instance.
+ /// True if the environment is Development or DiP mode is enabled; otherwise, false.
+ public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
- ///
- /// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
- ///
- /// The WebApplication instance.
- /// True if the environment is Development or DiP mode is enabled; otherwise, false.
- public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
+ ///
+ /// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
+ ///
+ /// The WebApplication instance.
+ /// True if the environment is Development or DiP mode is enabled; otherwise, false.
+ public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
///
/// Configures the services with options from the specified section of the appsettings.json file.
@@ -65,5 +62,4 @@ namespace DigitalData.Core.API
builder.Services.Configure(section);
return builder;
}
- }
-}
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/DigitalData.Core.API/DigitalData.Core.API.csproj b/DigitalData.Core.API/DigitalData.Core.API.csproj
index 22ad268..054f693 100644
--- a/DigitalData.Core.API/DigitalData.Core.API.csproj
+++ b/DigitalData.Core.API/DigitalData.Core.API.csproj
@@ -33,6 +33,7 @@
+
diff --git a/DigitalData.Core.API/ExceptionHandlingMiddleware.cs b/DigitalData.Core.API/ExceptionHandlingMiddleware.cs
new file mode 100644
index 0000000..b02655d
--- /dev/null
+++ b/DigitalData.Core.API/ExceptionHandlingMiddleware.cs
@@ -0,0 +1,84 @@
+namespace DigitalData.Core.API;
+
+using DigitalData.Core.Exceptions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using System.Text.Json;
+
+///
+/// Middleware for handling exceptions globally in the application.
+/// Captures exceptions thrown during the request pipeline execution,
+/// logs them, and returns an appropriate HTTP response with a JSON error message.
+///
+public class ExceptionHandlingMiddleware
+{
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The next middleware in the request pipeline.
+ /// The logger instance for logging exceptions.
+ public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
+
+ ///
+ /// Invokes the middleware to handle the HTTP request.
+ ///
+ /// The HTTP context of the current request.
+ /// A task that represents the asynchronous operation.
+ public async Task InvokeAsync(HttpContext context)
+ {
+ try
+ {
+ await _next(context); // Continue down the pipeline
+ }
+ catch (Exception ex)
+ {
+ await HandleExceptionAsync(context, ex, _logger);
+ }
+ }
+
+ ///
+ /// Handles exceptions by logging them and writing an appropriate JSON response.
+ ///
+ /// The HTTP context of the current request.
+ /// The exception that occurred.
+ /// The logger instance for logging the exception.
+ /// A task that represents the asynchronous operation.
+ private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
+ {
+ context.Response.ContentType = "application/json";
+
+ string message;
+
+ switch (exception)
+ {
+ case BadRequestException badRequestEx:
+ context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+ message = badRequestEx.Message;
+ break;
+
+ case NotFoundException notFoundEx:
+ context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ message = notFoundEx.Message;
+ break;
+
+ default:
+ logger.LogError(exception, "Unhandled exception occurred.");
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ message = "An unexpected error occurred.";
+ break;
+ }
+
+ await context.Response.WriteAsync(JsonSerializer.Serialize(new
+ {
+ message
+ }));
+ }
+}
diff --git a/DigitalData.Core.Exceptions/BadRequestException.cs b/DigitalData.Core.Exceptions/BadRequestException.cs
new file mode 100644
index 0000000..bd2ff27
--- /dev/null
+++ b/DigitalData.Core.Exceptions/BadRequestException.cs
@@ -0,0 +1,22 @@
+namespace DigitalData.Core.Exceptions;
+
+///
+/// Represents an exception that is thrown when a bad request is encountered.
+///
+public class BadRequestException : Exception
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BadRequestException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public BadRequestException(string? message) : base(message)
+ {
+ }
+}
diff --git a/DigitalData.Core.Exceptions/DigitalData.Core.Exceptions.csproj b/DigitalData.Core.Exceptions/DigitalData.Core.Exceptions.csproj
new file mode 100644
index 0000000..bae5b9f
--- /dev/null
+++ b/DigitalData.Core.Exceptions/DigitalData.Core.Exceptions.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net7.0;net8.0;net9.0
+ enable
+ enable
+
+
+
diff --git a/DigitalData.Core.Exceptions/NotFoundException.cs b/DigitalData.Core.Exceptions/NotFoundException.cs
new file mode 100644
index 0000000..7e7a0fa
--- /dev/null
+++ b/DigitalData.Core.Exceptions/NotFoundException.cs
@@ -0,0 +1,22 @@
+namespace DigitalData.Core.Exceptions;
+
+///
+/// Represents an exception that is thrown when a requested resource is not found.
+///
+public class NotFoundException : Exception
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotFoundException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public NotFoundException(string? message) : base(message)
+ {
+ }
+}
diff --git a/DigitalData.Core.sln b/DigitalData.Core.sln
index 3a9b04a..7ae921e 100644
--- a/DigitalData.Core.sln
+++ b/DigitalData.Core.sln
@@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastruc
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions", "DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj", "{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -100,6 +102,10 @@ Global
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -121,6 +127,7 @@ Global
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}