diff --git a/src/Codehard.Core.sln b/src/Codehard.Core.sln
index 71d1b72..d99b78c 100644
--- a/src/Codehard.Core.sln
+++ b/src/Codehard.Core.sln
@@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Common.Tests", "Co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.EntityFramework.Tests", "Codehard.Functional\Codehard.Functional.EntityFramework.Tests\Codehard.Functional.EntityFramework.Tests.csproj", "{317F58C6-2B02-468D-B3B3-78394A816D43}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codehard.Functional.AspNetCore.Tests.Api", "Codehard.Functional\Codehard.Functional.AspNetCore.Tests.Api\Codehard.Functional.AspNetCore.Tests.Api.csproj", "{959DF6C9-9D68-46E8-8BB1-6ACC3DE13053}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -173,6 +175,10 @@ Global
{317F58C6-2B02-468D-B3B3-78394A816D43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{317F58C6-2B02-468D-B3B3-78394A816D43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{317F58C6-2B02-468D-B3B3-78394A816D43}.Release|Any CPU.Build.0 = Release|Any CPU
+ {959DF6C9-9D68-46E8-8BB1-6ACC3DE13053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {959DF6C9-9D68-46E8-8BB1-6ACC3DE13053}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {959DF6C9-9D68-46E8-8BB1-6ACC3DE13053}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {959DF6C9-9D68-46E8-8BB1-6ACC3DE13053}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -208,5 +214,6 @@ Global
{C936B94D-D1D9-4A55-8CA1-748988282811} = {2F98EF48-527C-44C5-8FC3-65F25C808AC9}
{06431584-7320-4AEE-AC93-A22B422F543A} = {2F98EF48-527C-44C5-8FC3-65F25C808AC9}
{317F58C6-2B02-468D-B3B3-78394A816D43} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
+ {959DF6C9-9D68-46E8-8BB1-6ACC3DE13053} = {0C257E94-AD98-4AFB-93B7-B6F64EB7D2BA}
EndGlobalSection
EndGlobal
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Codehard.Functional.AspNetCore.Tests.Api.csproj b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Codehard.Functional.AspNetCore.Tests.Api.csproj
index a281ed7..8974154 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Codehard.Functional.AspNetCore.Tests.Api.csproj
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Codehard.Functional.AspNetCore.Tests.Api.csproj
@@ -4,6 +4,7 @@
net6.0
enable
enable
+ default
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Controllers/WeatherForecastController.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Controllers/WeatherForecastController.cs
index 04c79c4..b9615e6 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Controllers/WeatherForecastController.cs
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Controllers/WeatherForecastController.cs
@@ -13,11 +13,11 @@ public class WeatherForecastController : ControllerBase
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
- private readonly ILogger _logger;
+ private readonly ILogger logger;
public WeatherForecastController(ILogger logger)
{
- _logger = logger;
+ this.logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
@@ -40,6 +40,12 @@ public IActionResult GetSuccessEff()
return eff.RunToResult();
}
+ [HttpGet(template: "Get500WithThrowException")]
+ public IActionResult Get500WithThrowException()
+ {
+ throw new Exception("Error Msg");
+ }
+
[HttpGet(template: "Get500FailWithMsgEff")]
public IActionResult Get500FailWithMsgEff()
{
@@ -51,13 +57,24 @@ public IActionResult Get500FailWithMsgEff()
return eff.RunToResult();
}
+ [HttpGet(template: "Get500FailWithExceptionEff")]
+ public IActionResult Get500FailWithExceptionEff()
+ {
+ var eff =
+ Eff>(
+ () => throw new Exception("Error Msg"));
+
+ return eff.RunToResult();
+ }
+
[HttpGet(template: "Get500FailWithMsgAndErrCodeEff")]
public IActionResult Get500FailWithMsgAndErrCodeEff()
{
- var eff = FailEff>(HttpResultError.New(
- HttpStatusCode.InternalServerError,
- "Error Msg",
- errorCode: "Err001"));
+ var eff = FailEff>(
+ HttpResultError.New(
+ HttpStatusCode.InternalServerError,
+ "Error Msg",
+ errorCode: "Err001"));
return eff.RunToResult();
}
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Program.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Program.cs
index 8264bac..918ab90 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Program.cs
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Program.cs
@@ -1,8 +1,15 @@
+using Codehard.Functional.AspNetCore;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
+builder.Services.AddMvc(options =>
+{
+ options.Filters.Add();
+});
+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@@ -16,7 +23,7 @@
app.UseSwaggerUI();
}
-app.UseHttpsRedirection();
+//app.UseHttpsRedirection();
app.UseAuthorization();
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Properties/launchSettings.json b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Properties/launchSettings.json
index e4f07c3..2c04686 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Properties/launchSettings.json
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests.Api/Properties/launchSettings.json
@@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
- "applicationUrl": "https://localhost:7269;http://localhost:5159",
+ "applicationUrl": "http://localhost:5159",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs
index c5bb92c..44f4aa1 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs
@@ -24,13 +24,13 @@ private static IActionResult MapErrorToActionResult(Error err)
err switch
{
HttpResultError hre => new ErrorWrapperActionResult(hre),
- _ => new ObjectResult(err.Message)
- {
- StatusCode =
+ _ => new ErrorWrapperActionResult(
+ HttpResultError.New(
Enum.IsDefined(typeof(HttpStatusCode), err.Code)
- ? err.Code
- : (int)HttpStatusCode.InternalServerError,
- }
+ ? (HttpStatusCode)err.Code
+ : HttpStatusCode.InternalServerError,
+ err.Message,
+ error: err)),
};
}
@@ -50,18 +50,7 @@ public static IActionResult MatchToResult(
return fin
.Match(
res => MapToActionResult(successStatusCode, res),
- err =>
- {
- switch (err)
- {
- case HttpResultError hre:
- logger?.Log(hre);
- return MapErrorToActionResult(hre);
- default:
- logger?.Log(err);
- return MapErrorToActionResult(err);
- }
- });
+ MapErrorToActionResult);
}
///
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResult.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResult.cs
index 6a15ff0..c733900 100644
--- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResult.cs
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResult.cs
@@ -1,4 +1,5 @@
using System.Dynamic;
+using Microsoft.AspNetCore.Http;
namespace Codehard.Functional.AspNetCore;
@@ -34,7 +35,7 @@ public async Task ExecuteResultAsync(ActionContext context)
{
this.Error.ErrorCode.IfSome(errCode =>
context.HttpContext.Response.Headers.Add("x-error-code", errCode));
-
+
context.HttpContext.Response.Headers.Add("x-trace-id", context.HttpContext.TraceIdentifier);
await this.Error.Data
@@ -62,13 +63,15 @@ await this.Error.Data
})
.Match(
ar => ar.ExecuteResultAsync(context),
- None: () =>
+ None: async () =>
{
context.HttpContext.Response.StatusCode = (int)this.Error.StatusCode;
-
- return Task.CompletedTask;
+ context.HttpContext.Response.ContentType = "text/plain";
+ await context.HttpContext.Response.WriteAsync(context.HttpContext.TraceIdentifier);
});
+ return;
+
ObjectResult AddErrorInfo(ObjectResult objectResult)
{
IDictionary expando = new ExpandoObject();
diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResultLoggingFilter.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResultLoggingFilter.cs
new file mode 100644
index 0000000..c1aae28
--- /dev/null
+++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ErrorWrapperActionResultLoggingFilter.cs
@@ -0,0 +1,105 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+
+namespace Codehard.Functional.AspNetCore;
+
+///
+/// An action filter that logs errors occurring during the execution of an action.
+///
+public class ErrorWrapperActionResultLoggingFilter : IAsyncActionFilter
+{
+ private readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger to log error information.
+ public ErrorWrapperActionResultLoggingFilter(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ///
+ /// Executes the action and logs any errors that occur during the execution.
+ ///
+ /// The context in which the action is executed.
+ /// The delegate to execute the next action filter or action.
+ /// A representing the asynchronous operation.
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ var actionExecutedContext = await next();
+
+ await OnActionExecutedAsync(actionExecutedContext);
+ }
+
+ private Task OnActionExecutedAsync(ActionExecutedContext context)
+ {
+ var traceId = context.HttpContext.TraceIdentifier;
+
+ switch (context.Result)
+ {
+ case ErrorWrapperActionResult ewar:
+ LogHttpResultError(ewar.Error);
+ break;
+ case IStatusCodeActionResult statusCodeResult:
+ {
+ if(statusCodeResult.StatusCode == StatusCodes.Status500InternalServerError)
+ {
+ LogError(context.Exception);
+ }
+
+ break;
+ }
+ }
+
+ return Task.CompletedTask;
+
+ void LogHttpResultError(HttpResultError error)
+ {
+ this.logger.LogError(
+ message: "TraceId: {TraceId}, {Path}, {Query}, {Method}, {ResponseStatus}, {ErrorCode}",
+ traceId,
+ Sanitize(context.HttpContext.Request.Path),
+ Sanitize(context.HttpContext.Request.QueryString.Value),
+ Sanitize(context.HttpContext.Request.Method),
+ error.StatusCode,
+ error.ErrorCode.IfNoneUnsafe(default(string)));
+
+ LogErrorOpt(error.Inner);
+ }
+
+ void LogErrorOpt(Option errorOpt)
+ {
+ errorOpt.Iter(
+ Some: error =>
+ {
+ LogError(
+ exception: error.Exception.IfNoneUnsafe(default(Exception)));
+
+ LogErrorOpt(error.Inner);
+ });
+ }
+
+ void LogError(Exception? exception)
+ {
+ this.logger.LogError(
+ exception: exception,
+ message: "TraceId: {TraceId}, {Path}, {Query}, {Method}, {ResponseStatus}",
+ traceId,
+ Sanitize(context.HttpContext.Request.Path),
+ Sanitize(context.HttpContext.Request.QueryString.Value),
+ Sanitize(context.HttpContext.Request.Method),
+ HttpStatusCode.InternalServerError);
+ }
+ }
+
+ private static string Sanitize(string? input)
+ {
+ return new string(
+ input
+ ?.Replace(Environment.NewLine, "")
+ .Replace("\n", "")
+ .Replace("\r", ""));
+ }
+}
\ No newline at end of file
diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs
index aefda92..4ca4af8 100644
--- a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs
+++ b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs
@@ -17,7 +17,7 @@ private static Unit Log(this ILogger logger, Option errorOpt, LogLevel lo
return
error.Exception.Match(
- Some: ex => logger.LogError(ex, error.Message),
+ Some: ex => logger.LogError(ex, "{Message}", error.Message),
None: () =>
{
if (string.IsNullOrWhiteSpace(error.Message))