Skip to content

Asp.Net exception handling module

The exception handling module is comprised of these main components:

  • An exception handling middleware
  • An exception to http converter
  • An exception to problem details converter

The exception handling module sits on top of the ASP.NET Core execution pipeline and is responsible of handling the exception thrown by any component in the upstream execution pipeline.

Info

The exception handling module has some limitations: It depends on ASP.NET Core (there is no current support for Console Apps for example) and the only supported media type is json, actually application/problem+json, it has its own type, but is still json based.

The exception handling middleware performs several steps during the exception handling, which includes exception logging, translate the exception to its corresponding http status code using an IExceptionToHttpStatusCodeConverter instance, construct the ProblemDetails representation using the IExceptionToProblemDetailsConverter instance, and finally serializes the ProblemDetail instance using the current Mvc contract serializer options.

IExceptionToHttpStatusCodeConverter and IExceptionToProblemDetailsConverter implementations can be replaced supplying customized implementation, if needed. Take into account that IExceptionToProblemDetailsConverter provide several extensibility points in the form of protected virtual methods that can be overwritten if you do not want to create your implementation from the scratch.

Please continue reading the next sections to get a deeper understanding of the features and components offered by this module.

Configuring the module

The module is added by default to the ASP.NET Core pipeline if your application extends from SuiteAspNetApplicationModule. Nevertheless, if you want to do it by your own in your custom module implementation, or to configure the module options through ExceptionHandlingModuleOptions, you should do as following:

C#
1
2
3
4
5
builder.DependsOn<ExceptionHandlingModule, ExceptionHandlingModuleOptions>( options =>
{
    options.ProblemDetailsTypeBaseUri = "http://itsynch.com/myApp/errors";
    options.MapExceptionToHttpStatusCode<MyException>(HttpStatusCode.Conflict);
});

The ProblemDetailsTypeBaseUri must be set to the unique Uri base identifier defined for your application. It is unique for the overall application features, it runs at the module pipeline and any exception handled will use this base path to build up the resulting ProblemDetails' Type attribute value.

If you want to instruct the module to do specific mapping for some exceptions, you need to map them using the MapExceptionToHttpStatusCode of the ExceptionHandlingModuleOptions instance.

Customizing the module behavior

If you want to customize module behavior you can extend or replace the IExceptionToHttpStatusCodeConverter and/or IExceptionToProblemDetailsConverter which provides the conversions to HttpStatusCode the former and to ProblemDetails the latter.

Localizable exceptions

The framework provide a generic localizable exception called LocalizedException, which requires a generic type argument indicating the localization resource type identifier to get the localized resource from.

This exception, besides the said generic type parameter, requires the specification of a localization key to identify the localized text to be used when localization process is performed. Let's see an example:

C#
throw new LocalizedException<MyModuleResources>("This is the exception message.", "MSG_LOCALIZED_EXCEPTION");

From the previous example we can note that MyModuleResources is the resource bundle type identifier, and MSG_LOCALIZED_EXCEPTION is the localization key to be used to localize the exception message. The first argument is the exception message and is used for internal purposes only, such as logging, whereas the localized exception is what is more likely to be seen by the end user or displayed in the GUI.

Note that LocalizedException is a concrete class, not abstract. It means that you can use directly, as in the example shown before, nevertheless you should provide more meaningful exceptions instead of use the generic ones.

Using templated localization resources

The localized resource value may be a template instead of a static string. This template can provide placeholders for it replacement at runtime. The values used to fill in those placeholders are taken from the LocalizationData dictionary, where each placeholder must match wih the key of the item in the dictionary.

In the following example we can see the usage of the localization data dictionary to provide runtime values for the localization resource template identified by MSG_LOCALIZED_EXCEPTION_TEMPLATE key.

C#
1
2
3
4
5
6
7
8
// Create the exception.
var exception = new LocalizedException<MyModuleResources>("This is the exception message.", "MSG_LOCALIZED_EXCEPTION_TEMPLATE");

// Add localization data.
exception.LocalizationData["user"] = this.httpContext.User.Identity.Name ?? "Unknown";
exception.LocalizationData["time"] = DateTime.Now.TimeOfDay;

throw exception;

The json localization resource looks like this:

JSON
"MSG_LOCALIZED_EXCEPTION_TEMPLATE": "The user {user} produced an error at time {time} !"

Warn

The placeholders in the localization resource must be enclosed in brackets, and referencing the dictionary value by its key (eg. "..{itemKey1}...{itemKey2}"). This placeholder representation differs from the standard form using positional indexed placeholders ("[0]..[1]").

Exception logging

Exceptions can specify the error log level to be used during exception handling process. If you want to specify the level you should implement IHasErrorLogLevel interface and return the log level you want to be used for the logging process of your exception.

If you want to have a more fine grained logging process control, you can provide a specific logging behavior by implementing ISupportLog interface and provide the custom logging logic in the Log method. The logger instance will be provided at runtime by the Exception Handling Middleware.

Adding custom data to ProblemDetails

The ProblemDetails object can be used to hold and render extra data. These extra data values must be added by the application and helps the API client to know more about the error ocurred.

The Data dictionary is defined in Exception class, therefore all specialized exceptions types inherits this dictionary. Any value added to it will be pushed into the resulting ProblemDetails error object.

C#
1
2
3
4
5
6
var exception = new SomeException();

exception.Data["someData1"] = "Additional data 1";
exception.Data["someData2"] = "Additional data 2";

throw exception;

Exceptions with error codes

Exception can provide error codes to provide a more clearer understanding for the clients of what happened server-side. Error code will be appended to the end of the ProblemDetails' Type attribute.

For example given the value http://itsynch.com/myApp/errors for the ProblemDetailsTypeBaseUri attribute, if an exception provides the code MA-001, the resulting ProblemDetails' Type will be:

JSON
'type' = 'http://itsynch.com/myApp/errors/MA-001';

The usage of a standard error code nomenclature is encouraged.

Built-in provided exceptions

The Suite Framework provides some built-in implementations.

LocalizedException

This exception does not provide domain information about the error, it is intended to be used as base exception not to be used directly.

EntityNotFoundException

This exception has a meaning for the business: it is thrown when an entity is not found when we expected to found it. Typically when a search by identifier is done and we expect to encounter the entity, but we could not, and we can't go further from this point on.

This entity is localized at runtime providing locale aware information in the corresponding language, and admit two values:

  • EntityType: the entity type we were searching for.
  • EntityId: the id we were searching for.

EntityType and EntityId will be used to fill in the placeholders of the localization resource related with this exception.

EntityNotFoundException will be always translated to the Not Found HTTP status code, which is 404.

BusinessException

This exception represents an error in our business domain. This error implements IBusinessException (which in turns implements IHasErrorCode), ICanLocalizeExceptionDetails and extends from LocalizedException, this means that the exception can provide localized information about it message and details about it as well.

A more detailed example of a business exception is show here below:

C#
public InspectionCompletionRequiresAnswers(Inspection inspection)
    : base(code: "IINS-0129",
            message: "Error message to show in logs",
            messageLocalizationKey: "MSG_SOME_BUSINESS_EXCEPTION_MESSAGE_TEMPLATE",
            detailLocalizationKey: "MSG_SOME_BUSINESS_EXCEPTION_DETAIL_TEMPLATE")
{
    // Add localization data.
    LocalizationData["time"] = DateTime.Now.TimeOfDay;
    // Add extra data to the resulting Dto.
    Data["inspectionId"] = inspection.Id;
    Data["otherStuff"] = "For troubleshooting, or providing contextual info to clients.";
}

The example depict how to provide exception code, exception message and keys for message and detail localized values, supplied in the constructor. Also a localization value is provided to be used at runtime. Finally some extra data values are provided which will be rendered in the resulting ProblemDetails error object.

BusinessException will be always translated to the Bad Request HTTP status code, which is 400. If you want to know more about the type BusinessException you will need to use the Type attribute of the ProblemDetails error object.

Use a standard error code for Suite applications

The usage of a standard and uniform error naming rule is encouraged. Please use the following naming rule to identify error codes:

Bash
    {APP-CODE}-{ERROR_NUMBER}

Where: APP-CODE: is an alphanumeric value with four characters max. ERROR-NUMBER: is numeric value with six digits max, padding left spaces with zeroes.

For example:

  • AIMS-000001: stands for error 1 for AIMS application.
  • IINS-000105: stands for error 105 for iInspector application.