From cfc97c24e2a40eb3f2e5b293d7e640ca2d413e72 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Tue, 7 Jan 2025 20:35:58 +0100 Subject: [PATCH 1/2] Improve Boilerplate exception handling (#9635) --- .../Components/AppComponentBase.cs | 2 +- .../IClientCoreServiceCollectionExtensions.cs | 6 +++++- .../Services/AuthManager.cs | 2 +- .../Services/ClientExceptionHandlerBase.cs | 15 ++++++++++++++- .../Services/Contracts/IExceptionHandler.cs | 8 ++++++-- .../ExceptionDelegatingHandler.cs | 7 ++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index d98e8501aa..534a9691f1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -260,6 +260,6 @@ private void HandleException(Exception exp, } parameters["ComponentType"] = GetType().FullName; - ExceptionHandler.Handle(exp, displayKind: ExceptionDisplayKind.Interrupting, parameters, lineNumber, memberName, filePath); + ExceptionHandler.Handle(exp, ExceptionDisplayKind.Default, parameters, lineNumber, memberName, filePath); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs index cffdd82c3d..f5a9364dcb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs @@ -160,7 +160,11 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle if (string.IsNullOrEmpty(accessToken) is false && IAuthTokenProvider.ParseAccessToken(accessToken, validateExpiry: true).IsAuthenticated() is false) { - return await authManager.RefreshToken(requestedBy: nameof(HubConnectionBuilder)); + try + { + return await authManager.RefreshToken(requestedBy: nameof(HubConnectionBuilder)); + } + catch (ServerConnectionException) { /* If client gets disconnected and access token become expired, then this code will be called every few seconds and will show annoying error to the user. */ } } return accessToken; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs index 9861b41f62..887b195036 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs @@ -125,7 +125,7 @@ async Task RefreshTokenImplementation() { { "AdditionalData", "Refreshing access token failed." }, { "RefreshTokenRequestedBy", requestedBy } - }, displayKind: exp is ReusedRefreshTokenException ? ExceptionDisplayKind.NonInterrupting : ExceptionDisplayKind.Interrupting); + }); if (exp is UnauthorizedException // refresh token is also invalid. || exp is ReusedRefreshTokenException && refreshToken == await storageService.GetItem("refresh_token")) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientExceptionHandlerBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientExceptionHandlerBase.cs index b62543db6d..edb2d1ae6a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientExceptionHandlerBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientExceptionHandlerBase.cs @@ -12,7 +12,7 @@ public abstract partial class ClientExceptionHandlerBase : SharedExceptionHandle [AutoInject] protected readonly ILogger Logger = default!; public void Handle(Exception exception, - ExceptionDisplayKind displayKind = ExceptionDisplayKind.Interrupting, + ExceptionDisplayKind displayKind = ExceptionDisplayKind.Default, Dictionary? parameters = null, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", @@ -50,6 +50,11 @@ protected virtual void Handle(Exception exception, string exceptionMessageToShow = GetExceptionMessageToShow(exception); + if (displayKind is ExceptionDisplayKind.Default) + { + displayKind = GetDisplayKind(exception); + } + if (displayKind is ExceptionDisplayKind.NonInterrupting) { SnackBarService.Error("Boilerplate", exceptionMessageToShow); @@ -63,4 +68,12 @@ protected virtual void Handle(Exception exception, Debugger.Break(); } } + + private ExceptionDisplayKind GetDisplayKind(Exception exception) + { + if (exception is ServerConnectionException or ReusedRefreshTokenException) + return ExceptionDisplayKind.NonInterrupting; + + return ExceptionDisplayKind.Interrupting; + } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs index bcaff71916..3877057a12 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs @@ -15,13 +15,17 @@ public enum ExceptionDisplayKind /// /// Shows an auto-dismissed message (e.g., a toast notification) /// - NonInterrupting + NonInterrupting, + /// + /// Exception display kind gets chosen by exception type automatically. + /// + Default } public interface IExceptionHandler { void Handle(Exception exception, - ExceptionDisplayKind displayKind = ExceptionDisplayKind.Interrupting, + ExceptionDisplayKind displayKind = ExceptionDisplayKind.Default, Dictionary? parameters = null, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string memberName = "", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs index 1ba1de0d26..260984cb0c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs @@ -62,9 +62,10 @@ response.IsSuccessStatusCode is false && return response; } - catch (Exception exp) when ((exp is HttpRequestException && serverCommunicationSuccess is false) - || exp is TaskCanceledException tcExp && tcExp.InnerException is TimeoutException - || exp is HttpRequestException { StatusCode: HttpStatusCode.BadGateway or HttpStatusCode.GatewayTimeout or HttpStatusCode.ServiceUnavailable }) + catch (Exception exp) when ( + (exp is HttpRequestException && serverCommunicationSuccess is false) + || (exp is TaskCanceledException tcExp && tcExp.InnerException is TimeoutException) + || (exp is HttpRequestException { StatusCode: HttpStatusCode.BadGateway or HttpStatusCode.GatewayTimeout or HttpStatusCode.ServiceUnavailable })) { serverCommunicationSuccess = false; // Let's treat the server communication as failed if an exception is caught here. throw new ServerConnectionException(localizer[nameof(AppStrings.ServerConnectionException)], exp); From 69b5c583deb7be96e699d0097d8abac60125a9c7 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Tue, 7 Jan 2025 20:38:18 +0100 Subject: [PATCH 2/2] fix --- .../Extensions/IClientCoreServiceCollectionExtensions.cs | 3 ++- .../Services/Contracts/IExceptionHandler.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs index f5a9364dcb..38b07baf9e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs @@ -164,7 +164,8 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle { return await authManager.RefreshToken(requestedBy: nameof(HubConnectionBuilder)); } - catch (ServerConnectionException) { /* If client gets disconnected and access token become expired, then this code will be called every few seconds and will show annoying error to the user. */ } + catch (ServerConnectionException) + { } // If the client disconnects and the access token expires, this code will execute repeatedly every few seconds, causing an annoying error message to be displayed to the user. } return accessToken; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs index 3877057a12..d9167dc982 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/IExceptionHandler.cs @@ -17,7 +17,7 @@ public enum ExceptionDisplayKind /// NonInterrupting, /// - /// Exception display kind gets chosen by exception type automatically. + /// Automatically selects the exception display type based on the exception category. /// Default }