From 56726fb32e97cde88846d7da9460bb05113e7b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 29 Apr 2018 23:54:06 +0300 Subject: [PATCH] Continue to the exception handling doc. --- docs/Exception-Handling.md | 128 +++++++++++++++++- .../ExceptionHandling/AbpExceptionFilter.cs | 2 - .../Extensions/Logging/LoggerExtensions.cs | 16 +-- .../Abp/Logging/IExceptionCanLogDetails.cs | 9 -- .../Abp/Logging/IExceptionWithSelfLogging.cs | 9 ++ .../Abp/Validation/AbpValidationException.cs | 7 +- 6 files changed, 149 insertions(+), 22 deletions(-) delete mode 100644 src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionCanLogDetails.cs create mode 100644 src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionWithSelfLogging.cs diff --git a/docs/Exception-Handling.md b/docs/Exception-Handling.md index 08296e238a..23586965ef 100644 --- a/docs/Exception-Handling.md +++ b/docs/Exception-Handling.md @@ -1,3 +1,129 @@ ## Exception Handling -TODO \ No newline at end of file +ABP provides a built-in infrastructure and offers a standard model to exception handling for a web application. + +* Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request. +* Automatically hides **internal infrastructure errors**. +* Provides configuration option to **localize** exception messages. +* Automatically maps standard exceptions to **HTTP status codes** and provides configuration option to map custom exceptions. + +### Automatic Exception Handling + +`AbpExceptionFilter` handles an exception if **any of the following conditions** meets; + +* Exception is thrown by a **controller action** which returns an **object result** (not a view result). +* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). +* Client explicitly accepts the `application/json` content type (via `accept` HTTP header). + +If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. + +#### Error Message Format + +Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below: + +````json +{ + "error": { + "message": "This topic is locked and can not add a new message" + } +} +```` + +There are **optional fields** those can be filled based on the occurred exception. + +##### Error Code + +Error **code** is an optional and unique string value for the exception. Thrown `Exception` should implement the `IHasErrorCode` interface to fill this field. Example JSON value: + +````json +{ + "error": { + "code": "App:010042", + "message": "This topic is locked and can not add a new message" + } +} +```` + +Error code can also be used to localize the exception and customize the HTTP status code (see the related sections below). + +##### Error Details + +Error **details** in an optional field of the JSON error message. Thrown `Exception` should implement the `IHasErrorDetails` interface to fill this field. Example JSON value: + +```json +{ + "error": { + "code": "App:010042", + "message": "This topic is locked and can not add a new message", + "details": "A more detailed info about the error..." + } +} +``` + +##### Validation Errors + +**validationErrors** is a standard field that is filled if the thrown exception implements the `IHasValidationErrors` interface. + +````json +{ + "error": { + "code": "App:010046", + "message": "Your request is not valid, please correct and try again!", + "validationErrors": [{ + "message": "Username should be unique. 'john' is taken by another user!", + "members": ["userName"] + }, + { + "message": "Password is required", + "members": ["password"] + }] + } +} +```` + +`AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't deal with validation errors except you have a very custom validation logic. + +#### Logging + +Caught exceptions are automatically logged. + +##### Log Level + +Exceptions are logged with the `Error` level by default. Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example: + +````C# +public class MyException : Exception, IHasLogLevel +{ + public LogLevel LogLevel { get; set; } = LogLevel.Warning; + + //... +} +```` + +##### Self Logging Exceptions + +Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example: + +````C# +public class MyException : Exception, IExceptionWithSelfLogging +{ + public void Log(ILogger logger) + { + //...log additional info + } +} +```` + + + +TODO: + +Exception Interfaces (IHasLogLevel, ICanLogDetails, IHasErrorCode... etc.) + +Pre-Defined Base Exception Classes + +Standard Exception classes (AbpValidationException) + +Exception Localization + +HTTP Status Code Mapping \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index 76d685b8a8..8ff0846b74 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Primitives; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Json; diff --git a/src/Volo.Abp.Core/Microsoft/Extensions/Logging/LoggerExtensions.cs b/src/Volo.Abp.Core/Microsoft/Extensions/Logging/LoggerExtensions.cs index 1677e62090..4a75164486 100644 --- a/src/Volo.Abp.Core/Microsoft/Extensions/Logging/LoggerExtensions.cs +++ b/src/Volo.Abp.Core/Microsoft/Extensions/Logging/LoggerExtensions.cs @@ -82,32 +82,32 @@ namespace Microsoft.Extensions.Logging private static void LogDetails(ILogger logger, Exception exception) { - var loggingExceptions = new List(); + var loggingExceptions = new List(); - if (exception is IExceptionCanLogDetails) + if (exception is IExceptionWithSelfLogging) { - loggingExceptions.Add(exception as IExceptionCanLogDetails); + loggingExceptions.Add(exception as IExceptionWithSelfLogging); } else if (exception is AggregateException && exception.InnerException != null) { var aggException = exception as AggregateException; - if (aggException.InnerException is IExceptionCanLogDetails) + if (aggException.InnerException is IExceptionWithSelfLogging) { - loggingExceptions.Add(aggException.InnerException as IExceptionCanLogDetails); + loggingExceptions.Add(aggException.InnerException as IExceptionWithSelfLogging); } foreach (var innerException in aggException.InnerExceptions) { - if (innerException is IExceptionCanLogDetails) + if (innerException is IExceptionWithSelfLogging) { - loggingExceptions.AddIfNotContains(innerException as IExceptionCanLogDetails); + loggingExceptions.AddIfNotContains(innerException as IExceptionWithSelfLogging); } } } foreach (var ex in loggingExceptions) { - ex.LogDetails(logger); + ex.Log(logger); } } } diff --git a/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionCanLogDetails.cs b/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionCanLogDetails.cs deleted file mode 100644 index eabef4dc83..0000000000 --- a/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionCanLogDetails.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace Volo.Abp.Logging -{ - public interface IExceptionCanLogDetails - { - void LogDetails(ILogger logger); - } -} \ No newline at end of file diff --git a/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionWithSelfLogging.cs b/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionWithSelfLogging.cs new file mode 100644 index 0000000000..fe77497912 --- /dev/null +++ b/src/Volo.Abp.Core/Volo/Abp/Logging/IExceptionWithSelfLogging.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Logging +{ + public interface IExceptionWithSelfLogging + { + void Log(ILogger logger); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationException.cs b/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationException.cs index ea99c262cf..57d4202aa2 100644 --- a/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationException.cs +++ b/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationException.cs @@ -12,7 +12,10 @@ namespace Volo.Abp.Validation /// This exception type is used to throws validation exceptions. /// [Serializable] - public class AbpValidationException : AbpException, IHasLogLevel, IHasValidationErrors, IExceptionCanLogDetails + public class AbpValidationException : AbpException, + IHasLogLevel, + IHasValidationErrors, + IExceptionWithSelfLogging { /// /// Detailed list of validation errors for this exception. @@ -79,7 +82,7 @@ namespace Volo.Abp.Validation LogLevel = LogLevel.Warning; } - public void LogDetails(ILogger logger) + public void Log(ILogger logger) { if (ValidationErrors.IsNullOrEmpty()) {