![]() |
VOOZH | about |
dotnet add package Siemens.AspNet.ErrorHandling --version 7.5.6
NuGet\Install-Package Siemens.AspNet.ErrorHandling -Version 7.5.6
<PackageReference Include="Siemens.AspNet.ErrorHandling" Version="7.5.6" />
<PackageVersion Include="Siemens.AspNet.ErrorHandling" Version="7.5.6" />Directory.Packages.props
<PackageReference Include="Siemens.AspNet.ErrorHandling" />Project file
paket add Siemens.AspNet.ErrorHandling --version 7.5.6
#r "nuget: Siemens.AspNet.ErrorHandling, 7.5.6"
#:package Siemens.AspNet.ErrorHandling@7.5.6
#addin nuget:?package=Siemens.AspNet.ErrorHandling&version=7.5.6Install as a Cake Addin
#tool nuget:?package=Siemens.AspNet.ErrorHandling&version=7.5.6Install as a Cake Tool
The Siemens.AspNet.ErrorHandling package provides middleware and services for handling errors in ASP.NET Core
applications. It includes features for translating error messages based on the "Accepted Languages" header, making it
easier to build multilingual applications with standardized error responses. The package is designed to work seamlessly
with its companion package,
Siemens.AspNet.ErrorHandling.Contracts ,
which defines the core error handling models and base classes like ProblemDetails and ValidationProblemDetails.
By using RFC 7807, we ensure that error responses are consistent, easily interpretable by clients, and capable of conveying rich, structured information about errors. This approach enhances interoperability and helps developers diagnose issues more effectively.
You can install the package using NuGet:
dotnet add package Siemens.AspNet.ErrorHandling
Install-Package Siemens.AspNet.ErrorHandling
In your ASP.NET Core application, you need to register the error handling services in the Startup.cs or Program.cs file.
AddErrorHandling adds all services for handling response error and logging (see the example below).
If you want to add only the error logging, please add and use AddErrorLogHandling and UseErrorLogHandling.
If you want to add only the handler itself to handle the error responses, please add AddErrorResponseHandling and
UseErrorResponseHandling.
When using the AddErrorHandling method, it will also add the following service options:
builder.Services.Configure<RouteHandlerOptions>(options =>
{
options.ThrowOnBadRequest = true;
});
π§ Example Scenario:
With ThrowOnBadRequest = false
POST /api/submit
Body: { "age": "not_a_number" }
β 400 Bad Request (no exception thrown)
With ThrowOnBadRequest = true
POST /api/submit
Body: { "age": "not_a_number" }
β Throws exception (Model binding failure)
if you override the default behavior of the RouteHandlerOptions make sure to set the ThrowOnBadRequest=true!
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register the error handler services for logging or response handling
// services.AddErrorResponseHandling();
// services.AddErrorLogHandling();
// Register the error handling services
services.AddErrorHandling();
// Other service registrations...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Add the error response handling
// app.UseErrorResponseHandling();
// Add the error Logging
// app.UseErrorLogHandling();
// Add the error handling middleware to the request pipeline
app.UseErrorHandlingMiddleware();
// Other middleware...
}
}
var builder = WebApplication.CreateBuilder(args);
// Register the error handler services for logging or response handling
// builder.Services.AddErrorResponseHandling();
// builder.Services.AddErrorLogHandling();
// Register the error handling services
builder.Services.AddErrorHandling();
// Other service registrations...
var app = builder.Build();
app.UseRouting();
// Important to use the full potential setup the error handler after app.UseRouting();
app.UseErrorHandling();
// Other middleware...
app.Run();
If you use the Siemens.AspNet.MinimalApi.Sdk (coming soon) package everything is already setup. Just use the
ServerlessMinimalWebApi and everything
is ready to go.
using Pulse.FieldingTool.Api;
using Siemens.AspNet.MinimalApi.Sdk;
var webApi = new ServerlessMinimalWebApi();
webApi.RegisterServices = (service,
config) =>
{
// Domain service registrations
service.AddApi(config);
};
webApi.MapEndpoints = endpoints =>
{
// Map api domain endpoints
endpoints.MapApi();
};
webApi.Run(args);
// This is important that you are able to use
// API test via WebApplicationFactory<Program>
// https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0
namespace Pulse.FieldingTool
{
public partial class Program;
}
(Optional) A new feature has been introduced that allows you to control which error response will be processed and which will ne hidden from the user
To conifgure this feature, add the following configuration to your appsettings.json or set it as an environment variable:
Example: Single codes
"ErrorHandlerSettings": {
"ShowExceptionDetailsOnErrorCodeRanges": "200;201;202"
}
Example: Ranges (0-499)
"ErrorHandlerSettings": {
"ShowExceptionDetailsOnErrorCodeRanges": "0-400"
}
Example: Single numbers and ranges
"ErrorHandlerSettings": {
"ShowExceptionDetailsOnErrorCodeRanges": "200;210-299;400-499"
}
Default settings: if you are not configure this settings explicitly everything in the range from 0-499 will be handled and shown. All above will be hidden.
Sample: Handled response
{
"type": "ValidationProblemDetails",
"title": "Your patch request contains invalid data",
"status": 422,
"detail": "Your PatchFormsConfigurationRequest for the object id: 6f9e1c3e-8fbc-4d2a-9a3d-3c2e8f0b7d2f was invalid. Please check the error details. You might pass invalid data, or try to patch properties which not exists or not allow to patch",
"errors": {
"formsId": [
"FormsId must be either GUID or long.",
"FormsId must not be one or more whitespace. Only GUID or a long is valid for the ProjectId"
]
}
Sample: Hidden response
{
"type": "ProblemDetails",
"title": "An unexpected error occured, please contact the service",
"status": 422,
"detail": "An unexpected error occured, please contact the service",
"errors": {}
}
Extended Validation problem details:
endpoints.WithMetadata(new AllowedBodyMetaInfo(typeof(CreateFormsConfigurationRequest))); // Your request body type
ValidationProblemDetailsExtended``instead of ValidationProblemDetails` endpoints.Produces<ValidationProblemDetailsExtended>(StatusCodes.Status422UnprocessableEntity)
Sample endpoint:
internal static class CreateFormsConfigurationEndpoint
{
internal static void MapCreateFormsConfiguration(this IEndpointRouteBuilder endpoints)
{
endpoints.MapPost("formsConfigurations", HandleAsync)
.Produces<CreateFormsConfigurationResponse>()
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest)
.Produces<ProblemDetails>(StatusCodes.Status401Unauthorized)
.Produces<ProblemDetails>(StatusCodes.Status403Forbidden)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound)
.Produces<ProblemDetails>(StatusCodes.Status409Conflict)
.Produces<ValidationProblemDetailsExtended>(StatusCodes.Status422UnprocessableEntity)
.Produces<ProblemDetails>(StatusCodes.Status500InternalServerError)
.Produces<ProblemDetails>(StatusCodes.Status503ServiceUnavailable)
.Produces<string>(StatusCodes.Status504GatewayTimeout) // AWS handled error -> returns HTML
.WithTags("FormsConfigurations")
.WithName("createFormsConfigurationV1")
.MapToApiVersion(1)
.WithDescriptionFromFile("Description.txt")
.WithSummaryFromFile("Summary.txt")
.WithMetadata(new AllowedBodyMetaInfo(typeof(CreateFormsConfigurationRequest)));
static async Task<CreateFormsConfigurationResponse> HandleAsync(CreateFormsConfigurationRequest createFormsConfigurationRequest,
CreateFormsConfigurationCommand createFormsConfigurationCommand,
CancellationToken cancellationToken = default)
{
var project = await createFormsConfigurationCommand.ExecuteAsync(createFormsConfigurationRequest, cancellationToken).ConfigureAwait(false);
return new CreateFormsConfigurationResponse(project);
}
}
}
New outcome:
{
"type": "ValidationProblemDetailsExtended",
"title": "Your patch request contains invalid data",
"status": 422,
"detail": "Your PatchFormsConfigurationRequest for the object id: 6f9e1c3e-8fbc-4d2a-9a3d-3c2e8f0b7d2f was invalid. Please check the error details. You might pass invalid data, or try to patch properties which not exists or not allow to patch",
"errors": {
"formsId": [
"FormsId must be either GUID or long.",
"FormsId must not be one or more whitespace. Only GUID or a long is valid for the ProjectId"
]
},
"errorDetails": {
"formsId": {
"currentValue": " ",
"errors": [
"FormsId must be either GUID or long.",
"FormsId must not be one or more whitespace. Only GUID or a long is valid for the ProjectId"
],
"samples": [
"a1b2c3d4-e5f6-7890-1234-567890abcdef",
"1"
],
"location": null
}
},
"Id": "6f9e1c3e-8fbc-4d2a-9a3d-3c2e8f0b7d2f",
"RequestType": "PatchFormsConfigurationRequest",
"RequestContent": {
"formsId": " ",
"projectId": "c36b49ba-bf79-4c86-9d41-8c356634316e",
"title": "$UniqueFormsConfigurationName$",
"formsType": "InvitationOnly",
"startDate": "2025-03-15T18:32:18Z",
"endDate": "2025-04-14T18:23:44Z",
"sessionEndsIn": "00:30:00",
"hasUpdate": false,
"languages": [
{
"englishName": "English (United States)",
"nativeName": "English (United States)",
"tag": "en-US",
"tagThreeLetter": "eng",
"parentTag": "en"
}
],
"properties": {
"exampleKey": "ExampleValue"
}
}
}
Invalid Json:
To have even better json error responses if the json payload is invalid we added a new feature to handle this.
Error json:
{
"Id": {%$$},
"DisplayName": "$UniqueEdgeName$",
"SourceCapabilityId": "$SourceCapabilityId$",
"TargetCapabilityId": "$TargetCapabilityId$",
"RelationType": "Simple",
"Position": {
"x": 0,
"y": 1
},
"Size": {
"Width": 2,
"Height": 3
}
}
Orginal Minimal API Response: (Without this error handler)
{
"content": {
"headers": [],
"value": {}
},
"statusCode": "BadRequest",
"headers": [
{
"key": "api-supported-versions",
"value": [
"1"
]
}
],
"trailingHeaders": [],
"isSuccessStatusCode": false
}
With the error handler
{
"type":"ValidationProblemDetailsExtended",
"title":"JsonReaderException was thrown.",
"status":422,
"detail":"Invalid property identifier character: %",
"errorDetails":{
"id":{
"currentValue":"{%$$}",
"errors":[
"Invalid property identifier character: %"
],
"samples":[
"example text"
],
"location":{
"row":"2",
"column":"9"
}
}
},
"errors":{
"id":[
"Invalid property identifier character: % Location line number: \u00272\u0027 line position: \u00279\u0027"
]
}
}
Hint: SampleValues
If you have properties which presents for example an string but can be a Guid or number you can optimize the errors
thrown by System.Text.Json or Newtonsoft.Json easily by adding an attribute over your properties in your request.
This is very useful if you have any crash by your serializers before you enter your endpoint code
public sealed record UpdateFormsConfigurationRequest
{
[SampleValue("a1b2c3d4-e5f6-7890-1234-567890abcdef", "1")]
public required string FormsId { get; init; } // SDC --> FormsId GUID // Pulse --> SurveyInstanceId long
[SampleValue("a1b2c3d4-e5f6-7890-1234-567890abcdef", "1")]
public required string ProjectId { get; init; } // SDC --> GUID // Pulse -->
}
Special Case: ValidationProblemDetailsException
When the thrown exception is a ValidationProblemDetails exception, this configuration not only hides the details but also filters the validation errors themselves. This ensures that no validation error messages are exposed in the response.
With this configuration, whenever an error with the specified code (e.g., 400, 422, 500) or within the specified range ( 500-590) is thrown, detailed error messages will be hidden from the response.
(Optional)
The Siemens.AspNet.ErrorHandling package offers robust support for translating error messages based on the client's "
Accepted Languages" header. This is achieved through a pattern-based approach to localization keys, allowing for
consistent and easily maintainable translations across your application.
The Siemens.AspNet.ErrorHandling package automatically selects the appropriate language for error messages based on
the client's "Accepted-Languages" HTTP header. Hereβs how the language handling works:
For example, if the client requests de-DE (German for Germany) but only de.json is available, the package will use the translations from de.json. This fallback mechanism ensures that users receive the most appropriate localized content available.
To manage translations effectively, the Siemens.AspNet.ErrorHandling package uses JSON files for storing localized
error messages. Follow these guidelines to set up your translation files:
By organizing your translation files in this manner, you can maintain a clean and efficient structure that makes it easy to manage and update your localized content.
The translation keys for error messages follow a specific pattern, making it straightforward to manage translations for different exception types and scenarios. The pattern is as follows:
Variables:
Consider a scenario where you have a ValidationDetailsException in a class or method named MyAwesomeClass. The translation keys might look like this:
{
"MyAwesomeClass.ValidationDetailsException.Title.AUTH_TITLE_KEY": "This is my translated title",
"MyAwesomeClass.ValidationDetailsException.Details.AUTH_TITLE_KEY": "This is my translated details",
"MyAwesomeClass.ValidationDetailsException.Errors.AUTH_TITLE_KEY.Name.Name is required.": "This is my translated errors for name 1",
"MyAwesomeClass.ValidationDetailsException.Errors.AUTH_TITLE_KEY.Name.Name must be at least 3 characters long.": "This is my translated errors for name 2",
"MyAwesomeClass.ValidationDetailsException.Errors.AUTH_TITLE_KEY.Email.Email is not in a valid format.": "This is my translated errors for email"
}
This example shows how to throw an error in your c# code.
var errors = new Dictionary<string, string[]>
{
{ "Name", new[] { "Name is required.", "Name must be at least 3 characters long." } },
{ "Email", new[] { "Email is not in a valid format." } }
};
throw new ValidationDetailsException("AUTH_TITLE_KEY", "AUTH_DETAILS_KEY", errors);
The error enrichment system allows you to add custom metadata to error responses and logs before they are sent to clients or written to your logging infrastructure. This is useful for adding correlation IDs, environment information, user context, or any other application-specific data.
public class MyCustomResponseEnricher : IErrorResponseEnricher
{
public Task<ProblemDetails> EnrichAsync(ProblemDetails problemDetails,
HttpCallInfos httpCallInfos,
Exception exception)
{
// Add custom metadata
problemDetails.Extensions["timestamp"] = DateTime.UtcNow.ToString("o");
problemDetails.Extensions["environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
problemDetails.Extensions["traceId"] = httpCallInfos.TraceId;
return Task.FromResult(problemDetails);
}
}
services.AddSingleton<IErrorResponseEnricher, MyCustomResponseEnricher>();
That's it! The enricher will automatically be discovered and applied to all error responses.
The system provides two enrichment points:
IErrorResponseEnricher)Enriches ProblemDetails objects before they are written to the HTTP response:
public class CorrelationIdEnricher : IErrorResponseEnricher
{
public Task<ProblemDetails> EnrichAsync(ProblemDetails problemDetails,
HttpCallInfos httpCallInfos,
Exception exception)
{
problemDetails.Extensions["correlationId"] = Guid.NewGuid().ToString();
problemDetails.Extensions["requestPath"] = httpCallInfos.RequestPath;
return Task.FromResult(problemDetails);
}
}
IErrorLogEnricher)Enriches ErrorLogInfo objects before they are serialized and logged:
public class UserContextLogEnricher : IErrorLogEnricher
{
public Task<ErrorLogInfo> EnrichAsync(ErrorLogInfo errorLogInfo,
HttpCallInfos httpCallInfos,
Exception exception)
{
return Task.FromResult(errorLogInfo with
{
Extensions = errorLogInfo.Extensions
.Add("userId", httpCallInfos.HttpContext.User?.Identity?.Name ?? "anonymous")
.Add("ipAddress", httpCallInfos.HttpContext.Connection.RemoteIpAddress?.ToString())
});
}
}
Enrichers support async operations, enabling database lookups, external API calls, or other I/O operations:
public class AsyncMetadataEnricher(IMetadataService metadataService) : IErrorResponseEnricher
{
public async Task<ProblemDetails> EnrichAsync(ProblemDetails problemDetails,
HttpCallInfos httpCallInfos,
Exception exception)
{
// Perform async operation
var metadata = await metadataService.GetErrorMetadataAsync(exception.GetType());
problemDetails.Extensions["errorCategory"] = metadata.Category;
problemDetails.Extensions["recommendedAction"] = metadata.RecommendedAction;
return problemDetails;
}
}
Multiple enrichers are executed in the order they are registered:
services.AddSingleton<IErrorResponseEnricher, FirstEnricher>(); // Runs first
services.AddSingleton<IErrorResponseEnricher, SecondEnricher>(); // Runs second
services.AddSingleton<IErrorResponseEnricher, ThirdEnricher>(); // Runs third
Enrichment failures are automatically caught and logged by the default enrichment strategy. This ensures that enrichment errors don't break the main error handling flow:
// Even if this enricher throws an exception, the error response will still be sent
public class RiskyEnricher : IErrorResponseEnricher
{
public async Task<ProblemDetails> EnrichAsync(ProblemDetails problemDetails,
HttpCallInfos httpCallInfos,
Exception exception)
{
// If this throws, it won't break the error response
var data = await ExternalService.GetDataAsync();
problemDetails.Extensions["externalData"] = data;
return problemDetails;
}
}
If you need more control over how enrichers are executed (e.g., priority-based, conditional, or first-match-only), you can implement a custom strategy:
public class PriorityBasedEnrichmentStrategy : IErrorResponseEnrichmentStrategy
{
private readonly IEnumerable<IErrorResponseEnricher> _enrichers;
public PriorityBasedEnrichmentStrategy(IEnumerable<IErrorResponseEnricher> enrichers)
{
_enrichers = enrichers.OrderBy(e => GetPriority(e));
}
public async Task<ProblemDetails> EnrichAsync(ProblemDetails problemDetails,
HttpCallInfos httpCallInfos,
Exception exception)
{
var enriched = problemDetails;
foreach (var enricher in _enrichers)
{
enriched = await enricher.EnrichAsync(enriched, httpCallInfos, exception);
}
return enriched;
}
private int GetPriority(IErrorResponseEnricher enricher) { /* ... */ }
}
// Register your custom strategy
services.AddSingleton<IErrorResponseEnrichmentStrategy, PriorityBasedEnrichmentStrategy>();
The enrichment system is automatically integrated into the error handling pipeline:
ErrorHandlingStrategy before WriteResponseAsync()SpecificErrorLogHandler before serialization and loggingNo additional configuration or middleware setup is required beyond registering your enrichers in the DI container.
For more details on the interfaces and contract definitions, see the .
If you have the need to handle specific exceptions in a better way as the current handler supports you can implement your own specific error handler quite easy:
internal static class AddExceptionILikeToHandleHandlerExtension
{
public static void AddExceptionILikeToHandleHandler(this IServiceCollection services)
{
services.AddSingletonIfNotExists<ISpecificErrorHandler, ExceptionILikeToHandleHandler>();
}
}
internal sealed class ExceptionILikeToHandleHandler() : SpecificErrorHandler<ExceptionILikeToHandle>
{
protected override Task<ProblemDetails> HandleExceptionAsync(HttpContext context,
ExceptionILikeToHandle exception)
{
// Add here your custom logic, code to extract infos specific on your exception you like to handle in a specific way
var reducedProblemDetails = new ProblemDetails
{
Title = exception.Message,
Status = problemDetails.Status ?? StatusCodes.Status500InternalServerError,
Type = nameof(ProblemDetails),
Detail = exception.Message,
};
return Task.FromResult(exception.ProblemDetails);
}
}
using Pulse.FieldingTool.Api;
using Siemens.AspNet.MinimalApi.Sdk;
var webApi = new ServerlessMinimalWebApi();
webApi.RegisterServices = (service,
config) =>
{
// Domain service registrations
service.AddApi(config);
// Custom error handling
service.AddExceptionILikeToHandleHandler()
};
webApi.MapEndpoints = endpoints =>
{
// Map api domain endpoints
endpoints.MapApi();
};
webApi.Run(args);
If you have the need to handle specific exceptions in a better way as the current handler supports you can implement your own specific error log handler quite easy:
internal static class AddValidationProblemDetailsExtendedExceptionLogHandlerExtensions
{
public static void AddValidationProblemDetailsExtendedExceptionLogHandler(this IServiceCollection services)
{
services.AddExceptionLocalizerService();
services.AddTranslationService();
services.AddExceptionHelper();
services.AddSingletonIfNotExists<ISpecificErrorLogHandler, ValidationProblemDetailsExtendedExceptionLogHandler>();
}
}
internal sealed class ValidationProblemDetailsExtendedExceptionLogHandler(ILogger<DefaultExceptionLogHandler> logger,
JsonSerializerOptions jsonSerializerOptions,
ExceptionHelper exceptionHelper) : SpecificErrorLogHandler<ValidationProblemDetailsExtendedException>(logger, jsonSerializerOptions, exceptionHelper)
{
protected override Task<ErrorLogInfo> GetErrorLogFromAsync(HttpContext httpContext,
ValidationProblemDetailsExtendedException exception)
{
// Here you can implement what ever you like to log or customize it.
var errorLogInfo = new ErrorLogInfo
{
StatusCode = exception.ValidationProblemDetailsExtended.Status ?? 500,
Title = exception.ValidationProblemDetailsExtended.Title ?? "No title provided",
Message = exception.ValidationProblemDetailsExtended.Detail ?? "No details provided",
StackTrace = exception.StackTrace?.Split(Environment.NewLine).ToImmutableList() ?? ImmutableList<string>.Empty,
RequestInfos = httpContext.Request.GetQueryRequestInfo(exception.ValidationProblemDetailsExtended.Extensions).ToImmutableDictionary(k => k.key, v => v.value),
ErrorDetails = exception.ValidationProblemDetailsExtended.ErrorDetails.ToImmutableDictionary(k => k.Key, v => (object?)v.Value),
Extensions = exception.ValidationProblemDetailsExtended.Extensions.AsReadOnly(),
ErrorType = exception.GetType().Name
};
return Task.FromResult(errorLogInfo);
}
}
using Pulse.FieldingTool.Api;
using Siemens.AspNet.MinimalApi.Sdk;
var webApi = new ServerlessMinimalWebApi();
webApi.RegisterServices = (service,
config) =>
{
// Domain service registrations
service.AddApi(config);
// Custom error handling
service.AddValidationProblemDetailsExtendedExceptionLogHandler()
};
webApi.MapEndpoints = endpoints =>
{
// Map api domain endpoints
endpoints.MapApi();
};
webApi.Run(args);
Contributions are welcome! Please submit pull requests or issues as needed.
Please refer to the repository's license file.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on Siemens.AspNet.ErrorHandling:
| Package | Downloads |
|---|---|
|
Siemens.AspNet.MinimalApi.Sdk
A library which contains following functions: - Siemens.AspNet.MinimalApi.Sdk |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.5.6 | 81 | 6/17/2026 |
| 7.5.5 | 23 | 6/17/2026 |
| 7.5.4 | 24 | 6/17/2026 |
| 7.5.3 | 139 | 6/17/2026 |
| 7.5.2 | 50 | 6/16/2026 |
| 7.5.1 | 2,933 | 6/2/2026 |
| 7.5.0 | 351 | 5/12/2026 |
| 7.5.0-alpha.16 | 65 | 5/18/2026 |
| 7.5.0-alpha.15 | 57 | 5/18/2026 |
| 7.5.0-alpha.12 | 62 | 5/12/2026 |
| 7.5.0-alpha.11 | 58 | 5/12/2026 |
| 7.5.0-alpha.10 | 55 | 5/12/2026 |
| 7.5.0-alpha.3 | 66 | 5/11/2026 |
| 7.4.6 | 35 | 6/2/2026 |
| 7.4.5 | 563 | 5/24/2026 |
| 7.4.4 | 40 | 5/18/2026 |
| 7.4.3 | 41 | 5/18/2026 |
| 7.4.2 | 2,497 | 5/11/2026 |
| 7.4.1 | 38 | 5/11/2026 |
| 7.4.0 | 6,278 | 5/6/2026 |