How to read problem details JSON using HttpClient in C#
By FoxLearn 1/21/2025 9:05:55 AM 30
For example:
{ "type": "https://foxlearn.com/html/rfc7231#section-6.5.2", "title": "Invalid Payment Amount", "status": 422, "traceId": "abc123xyz456", "errors": { "Amount": [ "Amount must be greater than zero." ] } }
The example above is based on how ASP.NET Core typically returns model validation errors, using the ValidationProblemDetails
class.
Here’s an example of how to make a request to an API using HttpClient
, check the Content-Type
header to ensure it follows the problem details format, and then retrieve the content as a string:
var response = await httpClient.PostAsync(requestUrl, jsonContent); if (!response.IsSuccessStatusCode && response.Content.Headers.ContentType?.MediaType == "application/problem+json") { var problemDetailsJson = await response.Content.ReadAsStringAsync(); // Process the problem details }
The null-conditional operator (?.
) is used with ContentType
to avoid errors in case the Content-Type
header is not set.
There are several ways to use the problem details once retrieved:
- Log the error details.
- Display the error to the user.
- Deserialize the problem details JSON to:
- Show specific information (like just the error messages).
- Attempt to automatically resolve the issue based on the error details and retry the request (this is challenging but possible if the API provides machine-readable error messages).
Deserialize problem details JSON
First, Create a custom class for problem details:
public class PaymentProblemDetails : PaymentProblemDetailsWithErrors { public int InternalErrorCode { get; set; } } public class PaymentProblemDetailsWithErrors { public string Type { get; set; } public string Title { get; set; } public int Status { get; set; } public string TraceId { get; set; } public Dictionary<string, string[]> Errors { get; set; } }
Use HttpClient
to make a request and read the response:
var response = await httpClient.PostAsync(paymentUrl, paymentContent); if (!response.IsSuccessStatusCode && response.Content.Headers.ContentType?.MediaType == "application/problem+json") { var json = await response.Content.ReadAsStringAsync(); var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); // cache and reuse this // Deserialize the JSON into the custom class var problemDetails = JsonConvert.DeserializeObject<PaymentProblemDetails>(json, jsonOptions); Console.WriteLine($"Status: {problemDetails.Status}"); Console.WriteLine($"Title: {problemDetails.Title}"); // Check for specific errors in the response if (problemDetails.Errors.ContainsKey("Amount")) { Console.WriteLine($"Amount error: {string.Join(", ", problemDetails.Errors["Amount"])}"); } }
In this example:
- Custom Class (
PaymentProblemDetails
): This class mirrors the problem details structure returned by the API, with properties forType
,Title
,Status
,TraceId
, and a dictionary for errors. - HttpClient Call: The
PostAsync
method is used to send a payment request. If the response's content type isapplication/problem+json
, the JSON error details are read. - Error Deserialization: We use
JsonConvert.DeserializeObject
from Newtonsoft.Json to convert the problem details JSON into our customPaymentProblemDetails
class. - Output: The error details are printed to the console. In this case, it will show the error for the "Amount" field.
If the API response includes additional fields, such as internalErrorCode
, you can handle them by either:
- Subclassing the ProblemDetails class to include extra properties.
- Using the
[JsonExtensionData]
attribute to collect all additional properties in a dictionary.
For example, using the JsonExtensionData
attribute:
public class PaymentProblemDetailsWithErrors { public string Type { get; set; } public string Title { get; set; } public int Status { get; set; } public string TraceId { get; set; } public Dictionary<string, string[]> Errors { get; set; } [JsonExtensionData] public Dictionary<string, object> ExtensionData { get; set; } }
You can now deserialize the data and access the additional properties from the dictionary associated with the [JsonExtensionData]
attribute:
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); var problemDetails = JsonSerializer.Deserialize<PaymentProblemDetailsWithErrors>(json, jsonOptions); if (problemDetails.ExtensionData.TryGetValue("internalErrorCode", out var internalErrorCode)) { Console.WriteLine($"Internal error code: {internalErrorCode}"); }
Output:
Internal error code:-1
- How to Get and send JSON using HttpClient in C#
- How to consume an SSE endpoint using HttpClient in C#
- How to send a file using HttpClient in C#
- Sending query strings using HttpClient in C#
- Performance Boost from Reusing HttpClient Connections
- How to change the HttpClient timeout per request in C#
- Handling Redirects with HttpClient in C#
- How to Cancel an HttpClient Request in C#