Skip to content

Commit 632827b

Browse files
author
Vadim Belov
committed
Refactor IHttpError to use ProblemDetails for errors
- Move IHttpError to AspNetCore.Abstractions and update GetErrorModel to return ProblemDetails with optional traceId and path. - Remove SetTraceIdentifier; trace ID is now handled in error model generation. - Update WebApiException to generate ProblemDetails/ValidationProblemDetails with traceId, path, and extra context. - Make AddTraceId public and document its usage in ControllerBaseExtensions. - Update exception handler middleware to use new GetErrorModel signature and set content type to application/problem+json. - Standardize on ProblemDetails for error responses throughout the codebase.
1 parent fbe1f0e commit 632827b

5 files changed

Lines changed: 79 additions & 58 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025–2026 Vadim Belov <https://belov.us>
3+
4+
using Microsoft.AspNetCore.Mvc;
5+
using System.Net;
6+
7+
namespace EasyExtensions.AspNetCore.Abstractions
8+
{
9+
/// <summary>
10+
/// Interface for HTTP error.
11+
/// </summary>
12+
public interface IHttpError
13+
{
14+
/// <summary>
15+
/// Gets error model.
16+
/// </summary>
17+
/// <param name="traceId">Optional trace ID to include in the error model.</param>
18+
/// <param name="path">Optional request path to include in the error model.</param>
19+
/// <returns> Error model. </returns>
20+
ProblemDetails GetErrorModel(string? traceId = null, string? path = null);
21+
22+
/// <summary>
23+
/// Gets status code.
24+
/// </summary>
25+
HttpStatusCode StatusCode { get; }
26+
}
27+
}

Sources/EasyExtensions.AspNetCore/Exceptions/WebApiException.cs

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
// Copyright (c) 2025–2026 Vadim Belov <https://belov.us>
33

44
using EasyExtensions.Abstractions;
5+
using EasyExtensions.AspNetCore.Abstractions;
6+
using EasyExtensions.AspNetCore.Extensions;
57
using EasyExtensions.Models;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Mvc;
610
using Microsoft.AspNetCore.WebUtilities;
711
using System;
812
using System.Collections.Generic;
913
using System.Diagnostics;
1014
using System.Net;
15+
using System.Net.Mime;
16+
using System.Net.NetworkInformation;
1117

1218
namespace EasyExtensions.AspNetCore.Exceptions
1319
{
@@ -44,37 +50,48 @@ public class WebApiException(
4450
/// <summary>
4551
/// Additional error details. This property can be used to provide extra
4652
/// information about the error, such as validation errors, stack traces, or any
53+
/// other relevant context that may help in understanding the error.
4754
/// </summary>
4855
public object? Extra { get; } = extra;
4956

5057
/// <summary>
5158
/// Get error model.
5259
/// </summary>
5360
/// <returns> Error model. </returns>
54-
public ErrorModel GetErrorModel()
61+
public ProblemDetails GetErrorModel(string? traceId = null, string? path = null)
5562
{
56-
int statusCode = (int)StatusCode;
57-
return new()
63+
if (!string.IsNullOrWhiteSpace(ObjectName))
5864
{
59-
Status = statusCode,
60-
Type = GetRfcType(StatusCode),
61-
Title = ReasonPhrases.GetReasonPhrase(statusCode),
62-
TraceId = Activity.Current?.Id ?? string.Empty,
63-
Errors = new Dictionary<string, string>
65+
var details = new ValidationProblemDetails
6466
{
65-
{ ObjectName, Message }
66-
}
67+
Detail = this.Message,
68+
Instance = path ?? "/",
69+
Status = (int)StatusCode,
70+
Type = GetRfcType(StatusCode),
71+
Errors = new Dictionary<string, string[]>(),
72+
Extensions = new Dictionary<string, object?>(),
73+
Title = ReasonPhrases.GetReasonPhrase((int)StatusCode),
74+
};
75+
details.Extensions["traceId"] = traceId ?? Activity.Current?.Id ?? "-";
76+
ControllerBaseExtensions.AddExtra(details, Extra);
77+
details.Errors[ObjectName] = [Message];
78+
79+
return details;
80+
}
81+
82+
var problemDetails = new ProblemDetails
83+
{
84+
Detail = this.Message,
85+
Instance = path ?? "/",
86+
Status = (int)StatusCode,
87+
Type = GetRfcType(StatusCode),
88+
Extensions = new Dictionary<string, object?>(),
89+
Title = ReasonPhrases.GetReasonPhrase((int)StatusCode),
6790
};
68-
}
91+
problemDetails.Extensions["traceId"] = traceId ?? Activity.Current?.Id ?? "-";
92+
ControllerBaseExtensions.AddExtra(problemDetails, Extra);
6993

70-
/// <summary>
71-
/// Sets the trace identifier for the current context.
72-
/// </summary>
73-
/// <param name="traceId">The unique identifier to associate with the current trace. Cannot be null.</param>
74-
/// <exception cref="NotImplementedException">Thrown when the method is called, as the implementation is not provided.</exception>
75-
public void SetTraceIdentifier(string traceId)
76-
{
77-
throw new NotImplementedException();
94+
return problemDetails;
7895
}
7996

8097
private static string GetRfcType(HttpStatusCode statusCode)

Sources/EasyExtensions.AspNetCore/Extensions/ControllerBaseExtensions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,16 @@ private static ObjectResult BuildApiError(this ControllerBase controller, HttpSt
186186
};
187187
}
188188

189-
private static void AddTraceId(ControllerBase controller, ProblemDetails details)
189+
/// <summary>
190+
/// Adds the current HTTP request's trace identifier to the specified ProblemDetails instance as an extension
191+
/// for enhanced request tracking.
192+
/// </summary>
193+
/// <remarks>If the HTTP context does not contain a trace identifier or if it is empty or
194+
/// whitespace, no extension is added to the ProblemDetails instance. This method is useful for correlating
195+
/// problem responses with server logs during troubleshooting.</remarks>
196+
/// <param name="controller">The controller instance from which to obtain the HTTP context and its trace identifier. Cannot be null.</param>
197+
/// <param name="details">The ProblemDetails object to which the trace identifier extension will be added. Cannot be null.</param>
198+
public static void AddTraceId(ControllerBase controller, ProblemDetails details)
190199
{
191200
var traceId = controller.HttpContext.TraceIdentifier;
192201
if (!string.IsNullOrWhiteSpace(traceId))

Sources/EasyExtensions.AspNetCore/Extensions/ServiceCollectionExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22
// Copyright (c) 2025–2026 Vadim Belov <https://belov.us>
33

44
using EasyExtensions.Abstractions;
5+
using EasyExtensions.AspNetCore.Abstractions;
6+
using EasyExtensions.AspNetCore.Exceptions;
57
using EasyExtensions.AspNetCore.Formatters;
68
using EasyExtensions.AspNetCore.HealthChecks;
79
using EasyExtensions.Helpers;
810
using EasyExtensions.Services;
911
using Microsoft.AspNetCore.Diagnostics;
1012
using Microsoft.AspNetCore.Http;
13+
using Microsoft.AspNetCore.Mvc;
1114
using Microsoft.Extensions.Configuration;
1215
using Microsoft.Extensions.DependencyInjection;
1316
using Microsoft.Extensions.Logging;
1417
using Microsoft.Extensions.Logging.Console;
1518
using System;
1619
using System.Linq;
20+
using System.Net.Mime;
1721
using System.Threading.Tasks;
1822

1923
namespace EasyExtensions.AspNetCore.Extensions
@@ -199,13 +203,9 @@ private static async Task HandleException(HttpContext context)
199203
var exception = exceptionHandlerPathFeature.Error;
200204
if (exception is IHttpError httpError)
201205
{
202-
var traceId = context.TraceIdentifier;
203-
if (!string.IsNullOrEmpty(traceId))
204-
{
205-
httpError.SetTraceIdentifier(traceId);
206-
}
207206
context.Response.StatusCode = (int)httpError.StatusCode;
208-
await context.Response.WriteAsJsonAsync(httpError.GetErrorModel());
207+
context.Response.ContentType = MediaTypeNames.Application.ProblemJson;
208+
await context.Response.WriteAsJsonAsync(httpError.GetErrorModel(context.TraceIdentifier, context.Request.Path));
209209
}
210210
}
211211
}

Sources/EasyExtensions/Abstractions/IHttpError.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)