Siemens.AspNet.ErrorHandling 5.0.1

Prefix Reserved
This package has a SemVer 2.0.0 package version: 5.0.1+1.
dotnet add package Siemens.AspNet.ErrorHandling --version 5.0.1
                    
NuGet\Install-Package Siemens.AspNet.ErrorHandling -Version 5.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Siemens.AspNet.ErrorHandling" Version="5.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Siemens.AspNet.ErrorHandling" Version="5.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Siemens.AspNet.ErrorHandling" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Siemens.AspNet.ErrorHandling --version 5.0.1
                    
#r "nuget: Siemens.AspNet.ErrorHandling, 5.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=Siemens.AspNet.ErrorHandling&version=5.0.1
                    
Install Siemens.AspNet.ErrorHandling as a Cake Addin
#tool nuget:?package=Siemens.AspNet.ErrorHandling&version=5.0.1
                    
Install Siemens.AspNet.ErrorHandling as a Cake Tool

<h1><img src="../nuget-package-icon.png" alt="Siemens Icon" width="24" height="24"> Siemens.AspNet.ErrorHandling</h1>

Siemens.AspNet.ErrorHandling

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 Link to Documentation, 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.


πŸš€ Installation

You can install the package using NuGet:

.NET CLI

dotnet add package Siemens.AspNet.ErrorHandling

NuGet Package Manager Console

Install-Package Siemens.AspNet.ErrorHandling

πŸ“Œ Getting Started

1. Register the Middleware and Services

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.

Dotnet 7
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...
    }
}
Dotnet 8 or higher
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();
With Siemens.AspNet.MinimalApi.Sdk

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 Configuration

πŸ“ Show Error Details

(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

Configuration

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:

  1. Setup AllowedBodyMetaInfo at your endpoint You have to pass the type for your http content type (body). Only needed on http endpoints which have a body like POST, PUT, PATCH.
endpoints.WithMetadata(new AllowedBodyMetaInfo(typeof(CreateFormsConfigurationRequest))); // Your request body type
  1. Use 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"
    }
  }
}

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.

🌐 Translations

(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.

Language Handling

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:

  • <b>Language Selection:</b> The package reads the "Accepted-Languages" header from the client's request to determine the preferred language. This header can contain one or more language codes, and the package will attempt to match these codes with your available translation files.
  • <b>Default Language:</b> If the client's preferred language is not available or not specified, the package defaults to English (en).
  • <b>Fallback Mechanism:</b> The package leverages the CultureInfo class to handle language fallbacks. If a specific language variant (e.g., de-DE for German in Germany) is not available, the package will automatically fall back to the parent language (e.g., de for general German). This ensures that even if a specific translation is missing, the client still receives an error message in a related language.

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.

Translation Files

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:

  • <b>File Format</b>: The translation files are in JSON format, where each file corresponds to a specific language.
  • <b>Folder Organization</b>: Place your translation files in a dedicated folder like "Translations" or " ErrorTranslations". This helps you easily locate and manage your translation files.
  • <b>File Naming Convention</b>: Name your translation files based on the language code, such as de.json for German or en.json for English. This ensures clarity and consistency in your localization resources.

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.

Translation Key Pattern

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:

  • Title Translation Key: {Caller}.{ExceptionName}.Title.{TheExceptionTitle}
  • Details Translation Key: {Caller}.{ExceptionName}.Details.{TheExceptionTitle}
  • Errors Translation Key: {Caller}.{ExceptionName}.Errors.{TheExceptionTitle}.{ErrorKey}

Variables:

  • {Caller} - refers to the class or method that raised the exception.
  • {ExceptionName} - is the name of the exception class.
  • {TheExceptionTitle} - is a key representing the specific error context.
  • {ErrorKey} - represents the specific validation or error message key.
Example

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);

πŸ› οΈ Custom Handlers & Logging

πŸ”§ Custom Error Handler

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:

  1. Implement your specific error handler: You only have to provide a ProblemDetails object back. You have 3 levels of ProblemDetails:
  • ProblemDetails
  • ValidationProblemDetails
  • ValidationProblemDetailsExtended (best possible output for validations)
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);
    }
}
  1. Register your new service
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);

πŸ“‘ Custom Error Logging

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:

  1. Implement your specific error log handler: You only have to provide a ErrorLogInfo object back.
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);
    }
}
  1. Register your new service
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);

πŸ“š Additional Resources


🀝 Contributing

Contributions are welcome! Please submit pull requests or issues as needed.


πŸ“„ License

Please refer to the repository's license file.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
5.0.1 32 3/31/2025
5.0.0 23 3/31/2025
4.0.1 158 3/19/2025
4.0.0 356 3/6/2025
3.0.1 10,921 2/18/2025
3.0.0 216 2/17/2025
2.1.1 186 2/7/2025
2.1.0 10,190 1/27/2025
2.0.5 104 1/27/2025
2.0.4 57 1/17/2025
2.0.3 50 1/16/2025
2.0.2 733 1/16/2025
2.0.1 238 11/27/2024
2.0.0 81 11/19/2024
1.0.0 252 10/13/2024