diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj index 5fbcb19d8e..66ea1471db 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj @@ -46,6 +46,7 @@ + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs index 3b3754b075..acbb9e960d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs @@ -1,5 +1,4 @@ //+:cnd:noEmit -using Microsoft.Extensions.Logging; //#if (signalR == true) using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; @@ -8,6 +7,7 @@ //#if (appInsights == true) using BlazorApplicationInsights.Interfaces; //#endif +using Microsoft.AspNetCore.Components.Routing; namespace Boilerplate.Client.Core.Components; @@ -21,6 +21,7 @@ public partial class ClientAppCoordinator : AppComponentBase //#if (signalR == true) private HubConnection? hubConnection; [AutoInject] private Notification notification = default!; + [AutoInject] private ILogger signalRLogger = default!; //#endif //#if (notification == true) [AutoInject] private IPushNotificationService pushNotificationService = default!; @@ -31,18 +32,31 @@ public partial class ClientAppCoordinator : AppComponentBase [AutoInject] private Navigator navigator = default!; [AutoInject] private IJSRuntime jsRuntime = default!; [AutoInject] private IStorageService storageService = default!; - [AutoInject] private ILogger logger = default!; [AutoInject] private AuthenticationManager authManager = default!; + [AutoInject] private ILogger navigatorLogger = default!; [AutoInject] private CultureInfoManager cultureInfoManager = default!; [AutoInject] private ILogger authLogger = default!; [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; protected override async Task OnInitAsync() { - AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged; + if (AppPlatform.IsBlazorHybrid) + { + if (CultureInfoManager.MultilingualEnabled) + { + cultureInfoManager.SetCurrentCulture(new Uri(NavigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture + await storageService.GetItem("Culture") ?? // 2- User settings + CultureInfo.CurrentUICulture.Name); // 3- OS settings + } + + await SetupBodyClasses(); + } if (InPrerenderSession is false) { + NavigationManager.LocationChanged += NavigationManager_LocationChanged; + AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged; + TelemetryContext.UserAgent = await navigator.GetUserAgent(); TelemetryContext.TimeZone = await jsRuntime.GetTimeZone(); TelemetryContext.Culture = CultureInfo.CurrentCulture.Name; @@ -66,21 +80,14 @@ protected override async Task OnInitAsync() AuthenticationStateChanged(AuthenticationManager.GetAuthenticationStateAsync()); } - if (AppPlatform.IsBlazorHybrid) - { - if (CultureInfoManager.MultilingualEnabled) - { - cultureInfoManager.SetCurrentCulture(new Uri(NavigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture - await storageService.GetItem("Culture") ?? // 2- User settings - CultureInfo.CurrentUICulture.Name); // 3- OS settings - } - - await SetupBodyClasses(); - } - await base.OnInitAsync(); } + private void NavigationManager_LocationChanged(object? sender, LocationChangedEventArgs e) + { + navigatorLogger.LogInformation("Navigation's location changed to {Location}", e.Location); + } + private async void AuthenticationStateChanged(Task task) { try @@ -166,7 +173,7 @@ private async Task ConnectSignalR() hubConnection.On(SignalREvents.PUBLISH_MESSAGE, async (message) => { - logger.LogInformation("Message {Message} received from server.", message); + signalRLogger.LogInformation("Message {Message} received from server.", message); PubSubService.Publish(message); }); @@ -190,7 +197,7 @@ private async Task ConnectSignalR() private async Task HubConnectionConnected(string? connectionId) { PubSubService.Publish(ClientPubSubMessages.IS_ONLINE_CHANGED, true); - logger.LogInformation("SignalR connection {ConnectionId} established.", connectionId); + signalRLogger.LogInformation("SignalR connection {ConnectionId} established.", connectionId); } private async Task HubConnectionDisconnected(Exception? exception) @@ -199,16 +206,20 @@ private async Task HubConnectionDisconnected(Exception? exception) if (exception is null) { - logger.LogInformation("SignalR connection lost."); // Was triggered intentionally by either server or client. + signalRLogger.LogInformation("SignalR connection lost."); // Was triggered intentionally by either server or client. } else { + signalRLogger.LogWarning(exception, "SignalR connection lost."); + if (exception is HubException && exception.Message.EndsWith(nameof(AppStrings.UnauthorizedException))) { - await AuthenticationManager.RefreshToken(CurrentCancellationToken); + try + { + await AuthenticationManager.RefreshToken(CurrentCancellationToken); + } + catch { } } - - logger.LogError(exception, "SignalR connection lost."); } } @@ -244,6 +255,7 @@ private async Task SetupBodyClasses() protected override async ValueTask DisposeAsync(bool disposing) { + NavigationManager.LocationChanged -= NavigationManager_LocationChanged; AuthenticationManager.AuthenticationStateChanged -= AuthenticationStateChanged; //#if (signalR == true) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor index e9e4138769..a7568db0a9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor @@ -1,4 +1,4 @@ -@inherits AppComponentBase +@inherits AppComponentBase
@@ -58,7 +58,7 @@ Variant="BitVariant.Text" IconName="@BitIconName.Copy" OnClick="() => CopyException(logIndex.item)" /> - @logIndex.item.Message + @($"{logIndex.item.Category}: {logIndex.item.Message}") diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor.cs index c6d91f0a44..9e6e616f4c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/DiagnosticModal.razor.cs @@ -1,5 +1,4 @@ using Boilerplate.Client.Core.Services.DiagnosticLog; -using Microsoft.Extensions.Logging; namespace Boilerplate.Client.Core.Components.Layout; @@ -59,7 +58,7 @@ private void HandleOnSortClick() private void FilterLogs() { - filteredLogs = allLogs.WhereIf(string.IsNullOrEmpty(searchText) is false, l => l.Message?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true) + filteredLogs = allLogs.WhereIf(string.IsNullOrEmpty(searchText) is false, l => l.Message?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true || l.Category?.Contains(searchText!, StringComparison.InvariantCultureIgnoreCase) is true) .Where(l => filterLogLevels.Contains(l.Level)); if (isDescendingSort) { @@ -80,7 +79,7 @@ private async Task CopyException(DiagnosticLog log) { var stateToCopy = string.Join(Environment.NewLine, log.State?.Select(i => $"{i.Key}: {i.Value}") ?? []); - await clipboard.WriteText($"{log.Message}{Environment.NewLine}{log.Exception?.ToString()}{Environment.NewLine}{stateToCopy}"); + await clipboard.WriteText($"{log.Category}{Environment.NewLine}{log.Message}{Environment.NewLine}{log.Exception?.ToString()}{Environment.NewLine}{stateToCopy}"); } private async Task GoTop() diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/ConfirmPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/ConfirmPage.razor.cs index ef0fd495cf..0a30948be9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/ConfirmPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/ConfirmPage.razor.cs @@ -77,7 +77,7 @@ await WrapRequest(async () => { var signInResponse = await identityController.ConfirmEmail(new() { Email = emailModel.Email, Token = emailModel.Token }, CurrentCancellationToken); - await AuthenticationManager.OnNewToken(signInResponse, true); + await AuthenticationManager.StoreTokens(signInResponse, true); NavigationManager.NavigateTo(Urls.HomePage, replace: true); @@ -103,7 +103,7 @@ await WrapRequest(async () => { var signInResponse = await identityController.ConfirmPhone(new() { PhoneNumber = phoneModel.PhoneNumber, Token = phoneModel.Token }, CurrentCancellationToken); - await AuthenticationManager.OnNewToken(signInResponse, true); + await AuthenticationManager.StoreTokens(signInResponse, true); NavigationManager.NavigateTo(Urls.HomePage, replace: true); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/NotAuthorizedPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/NotAuthorizedPage.razor.cs index ba7aa38d2c..b620f96593 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/NotAuthorizedPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/NotAuthorizedPage.razor.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; - -namespace Boilerplate.Client.Core.Components.Pages; +namespace Boilerplate.Client.Core.Components.Pages; public partial class NotAuthorizedPage { @@ -9,8 +7,6 @@ public partial class NotAuthorizedPage [SupplyParameterFromQuery(Name = "return-url"), Parameter] public string? ReturnUrl { get; set; } - [AutoInject] private ILogger logger = default!; - protected override async Task OnParamsSetAsync() { user = (await AuthenticationStateTask).User; @@ -43,8 +39,6 @@ protected override async Task OnAfterFirstRenderAsync() isRefreshingToken = false; } - logger.LogInformation("Refreshing access token."); - if (ReturnUrl is not null) { var @char = ReturnUrl.Contains('?') ? '&' : '?'; // The RedirectUrl may already include a query string. 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 1ff00f7094..6a78913d5e 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 @@ -58,14 +58,16 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle // handlers, such as ASP.NET Core's `HttpMessageHandler` from the Test Host, which is useful for integration tests. services.AddScoped(serviceProvider => transportHandler => { - var constructedHttpMessageHandler = ActivatorUtilities.CreateInstance(serviceProvider, + var constructedHttpMessageHandler = ActivatorUtilities.CreateInstance(serviceProvider, + [ActivatorUtilities.CreateInstance(serviceProvider, [ActivatorUtilities.CreateInstance(serviceProvider, [ActivatorUtilities.CreateInstance(serviceProvider, - [ActivatorUtilities.CreateInstance(serviceProvider, [transportHandler])])])]); + [ActivatorUtilities.CreateInstance(serviceProvider, [transportHandler])])])])]); return constructedHttpMessageHandler; }); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(serviceProvider => diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthenticationManager.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthenticationManager.cs index 5d7a3f22cf..39eaaf3d06 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthenticationManager.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthenticationManager.cs @@ -19,6 +19,7 @@ public partial class AuthenticationManager : AuthenticationStateProvider [AutoInject] private IExceptionHandler exceptionHandler = default!; [AutoInject] private IStringLocalizer localizer = default!; [AutoInject] private IIdentityController identityController = default!; + [AutoInject] private ILogger authLogger = default!; /// /// Sign in and return whether the user requires two-factor authentication. @@ -30,38 +31,51 @@ public async Task SignIn(SignInRequestDto request, CancellationToken cance if (response.RequiresTwoFactor) return true; - await OnNewToken(response!, request.RememberMe); + await StoreTokens(response!, request.RememberMe); return false; } - public async Task OnNewToken(TokenResponseDto response, bool? rememberMe = null) + public async Task StoreTokens(TokenResponseDto response, bool? rememberMe = null) { - await StoreTokens(response, rememberMe); + if (rememberMe is null) + { + rememberMe = await storageService.IsPersistent("refresh_token"); + } - var state = await GetAuthenticationStateAsync(); + await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true); + await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true); + + if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized()) + { + await cookie.Set(new() + { + Name = "access_token", + Value = response.AccessToken, + MaxAge = rememberMe is true ? response.ExpiresIn : null, // to create a session cookie + Path = "/", + SameSite = SameSite.Strict, + Secure = AppEnvironment.IsDev() is false + }); + } - NotifyAuthenticationStateChanged(Task.FromResult(state)); + NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync())); } public async Task SignOut(CancellationToken cancellationToken) { try { - if (string.IsNullOrEmpty(await storageService.GetItem("access_token")) is false) - { - await userController.SignOut(cancellationToken); - } + await userController.SignOut(cancellationToken); + } + catch (Exception exp) when (exp is ServerConnectionException or UnauthorizedException) + { + // The user might sign out while the app is offline, making token refresh attempts fail. + // These exceptions are intentionally ignored in this case. } finally { - await storageService.RemoveItem("access_token"); - await storageService.RemoveItem("refresh_token"); - if (AppPlatform.IsBlazorHybrid is false) - { - await cookie.Remove("access_token"); - } - NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync())); + await ClearTokens(); } } @@ -70,7 +84,7 @@ public async Task RefreshToken(CancellationToken cancellationToken) try { await semaphore.WaitAsync(); - + authLogger.LogInformation("Refreshing access token"); try { string? refresh_token = await storageService.GetItem("refresh_token"); @@ -78,23 +92,17 @@ public async Task RefreshToken(CancellationToken cancellationToken) throw new UnauthorizedException(localizer[nameof(AppStrings.YouNeedToSignIn)]); var refreshTokenResponse = await identityController.Refresh(new() { RefreshToken = refresh_token }, cancellationToken); - await StoreTokens(refreshTokenResponse!); - return refreshTokenResponse.AccessToken; + await StoreTokens(refreshTokenResponse); + return refreshTokenResponse.AccessToken!; } catch (UnauthorizedException) // refresh_token is either invalid or expired. { - if (AppPlatform.IsBlazorHybrid is false) - { - await cookie.Remove("access_token"); - } - await storageService.RemoveItem("access_token"); - await storageService.RemoveItem("refresh_token"); + await ClearTokens(); throw; } } finally { - NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync())); semaphore.Release(); } } @@ -123,27 +131,14 @@ public override async Task GetAuthenticationStateAsync() } } - private async Task StoreTokens(TokenResponseDto response, bool? rememberMe = null) + private async Task ClearTokens() { - if (rememberMe is null) - { - rememberMe = await storageService.IsPersistent("refresh_token"); - } - - await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true); - await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true); - - if (AppPlatform.IsBlazorHybrid is false && jsRuntime.IsInitialized()) + await storageService.RemoveItem("access_token"); + await storageService.RemoveItem("refresh_token"); + if (AppPlatform.IsBlazorHybrid is false) { - await cookie.Set(new() - { - Name = "access_token", - Value = response.AccessToken, - MaxAge = rememberMe is true ? response.ExpiresIn : null, // to create a session cookie - Path = "/", - SameSite = SameSite.Strict, - Secure = AppEnvironment.IsDev() is false - }); + await cookie.Remove("access_token"); } + NotifyAuthenticationStateChanged(Task.FromResult(await GetAuthenticationStateAsync())); } } 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 95dc616f96..87795acfa1 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 @@ -1,6 +1,5 @@ //+:cnd:noEmit using System.Diagnostics; -using Microsoft.Extensions.Logging; using System.Runtime.CompilerServices; namespace Boilerplate.Client.Core.Services; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs index 2722fdf4c7..9221921744 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs @@ -20,10 +20,12 @@ public async Task ChangeCulture(string? cultureName) { await cookie.Set(new() { - MaxAge = 30 * 24 * 3600, Name = ".AspNetCore.Culture", - Secure = AppEnvironment.IsDev() is false, Value = Uri.EscapeDataString($"c={cultureName}|uic={cultureName}"), + MaxAge = 30 * 24 * 3600, + Path = "/", + SameSite = SameSite.Strict, + Secure = AppEnvironment.IsDev() is false }); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLog.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLog.cs index a3b76bbb0a..e11d4ae726 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLog.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLog.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; - -namespace Boilerplate.Client.Core.Services.DiagnosticLog; +namespace Boilerplate.Client.Core.Services.DiagnosticLog; public class DiagnosticLog { @@ -10,6 +8,8 @@ public class DiagnosticLog public string? Message { get; set; } + public string? Category { get; set; } + public Exception? Exception { get; set; } public IDictionary? State { get; set; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLogger.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLogger.cs index 9f25214154..e88cc29cd1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLogger.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLogger.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; - -namespace Boilerplate.Client.Core.Services.DiagnosticLog; +namespace Boilerplate.Client.Core.Services.DiagnosticLog; public partial class DiagnosticLogger : ILogger, IDisposable { @@ -8,7 +6,7 @@ public partial class DiagnosticLogger : ILogger, IDisposable private IDictionary? currentState; - public string? CategoryName { get; set; } + public string? Category { get; set; } public IDisposable? BeginScope(TState state) where TState : notnull @@ -32,7 +30,15 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except var message = formatter(state, exception); - Store.Add(new() { CreatedOn = DateTimeOffset.Now, Level = logLevel, Message = message, Exception = exception, State = currentState?.ToDictionary(i => i.Key, i => i.Value?.ToString()) }); + Store.Add(new() + { + CreatedOn = DateTimeOffset.Now, + Level = logLevel, + Message = message, + Exception = exception, + Category = Category, + State = currentState?.ToDictionary(i => i.Key, i => i.Value?.ToString()) + }); } public void Dispose() diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLoggerProvider.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLoggerProvider.cs index 74b2cccbd9..e0170b10ff 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLoggerProvider.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/DiagnosticLog/DiagnosticLoggerProvider.cs @@ -1,5 +1,4 @@ //+:cnd:noEmit -using Microsoft.Extensions.Logging; namespace Boilerplate.Client.Core.Services.DiagnosticLog; @@ -16,7 +15,7 @@ public ILogger CreateLogger(string categoryName) { return new DiagnosticLogger() { - CategoryName = categoryName + Category = categoryName }; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/HttpMessageHandlersChainFactory.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/HttpMessageHandlersChainFactory.cs index 8ed58f0f44..f62e4d81f9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/HttpMessageHandlersChainFactory.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/HttpMessageHandlersChainFactory.cs @@ -7,10 +7,11 @@ /// and returns a chain of handlers that will process HTTP messages sequentially. /// The returned chain will include the following handlers, in order: /// -/// 1. -/// 2. -/// 3. -/// 4. +/// 1. +/// 2. +/// 3. +/// 4. +/// 5. /// /// The chain is constructed in reverse order, with the provided `transportHandler` as the final /// link. Each subsequent handler in the chain receives the output of the previous one. diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/LoggingDelegatingHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/LoggingDelegatingHandler.cs new file mode 100644 index 0000000000..da6a471c21 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/LoggingDelegatingHandler.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; + +namespace Boilerplate.Client.Core.Services.HttpMessageHandlers; + +internal class LoggingDelegatingHandler(ILogger logger, HttpMessageHandler handler) + : DelegatingHandler(handler) +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + logger.LogInformation("Sending HTTP request {Method} {Uri}", request.Method, request.RequestUri); + + var stopwatch = Stopwatch.StartNew(); + int? responseStatusCode = null; + try + { + var response = await base.SendAsync(request, cancellationToken); + responseStatusCode = (int?)response.StatusCode; + return response; + } + catch (RestException exp) + { + responseStatusCode = (int)exp.StatusCode; + throw; + } + catch (HttpRequestException exp) + { + responseStatusCode = (int?)exp.StatusCode; + throw; + } + finally + { + logger.Log(responseStatusCode is null or >= 400 ? LogLevel.Warning : LogLevel.Information, "Received HTTP response for {Uri} after {Duration}ms - {StatusCode}", + request.RequestUri, + stopwatch.ElapsedMilliseconds, + responseStatusCode); + } + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs index 22fe69ae15..5265598f92 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs @@ -1,6 +1,5 @@ using Boilerplate.Shared.Dtos.PushNotification; using Boilerplate.Shared.Controllers.PushNotification; -using Microsoft.Extensions.Logging; namespace Boilerplate.Client.Core.Services; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs index f03bbe9e29..20002b6faa 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs @@ -26,10 +26,12 @@ public async Task ToggleTheme() await cookie.Set(new() { - MaxAge = 30 * 24 * 3600, Name = "bit.Theme", - Secure = AppEnvironment.IsDev() is false, Value = Uri.EscapeDataString(newThemeName), + MaxAge = 30 * 24 * 3600, + Path = "/", + SameSite = SameSite.Strict, + Secure = AppEnvironment.IsDev() is false }); return theme; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs index 88a97b1f5f..5d219c09a4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs @@ -218,14 +218,12 @@ public async Task> Refresh(RefreshRequestDto requ } finally { - try + if (user is not null) { - if (user is not null) - { - await userManager.UpdateAsync(user); - } + var result = await userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new ResourceValidationException(result.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); } - catch (ConflictException) { /* When access_token gets expired and user navigates to the page that sends multiple requests in parallel, multiple concurrent refresh token api call happens and this will results into concurrency exception during updating session's renewed on. */ } } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs index 0b49fd0eae..6df512bf92 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs @@ -96,7 +96,7 @@ public async Task RequestPush(string? title = null, string? message = null, stri } catch (Exception exp) { - logger.LogError(exp, "Failed to send push notification."); + logger.LogWarning(exp, "Failed to send push notification."); } } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.Development.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.Development.json index f57da75893..3424327089 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.Development.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.Development.json @@ -4,7 +4,8 @@ "ApplicationInsights": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, @@ -20,7 +21,8 @@ "AppCenterLoggerProvider": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, @@ -28,7 +30,8 @@ "Console": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, @@ -42,28 +45,32 @@ "EventLog": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, "EventSource": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, "DiagnosticLogger": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true }, "Debug": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "System.Net.Http.HttpClient.*.LogicalHandler": "Warning" }, "IncludeScopes": true } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json index 2ecf726374..645bb0d2e0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json @@ -19,6 +19,7 @@ "ApplicationInsightsLoggerProvider": { "LogLevel": { "Default": "Warning", + "System.Net.Http": "None", "Microsoft.EntityFrameworkCore.Database.Command": "Information", "Boilerplate.Client.Core.Services.AuthenticationManager": "Information" }, @@ -45,6 +46,7 @@ "WebAssemblyConsoleLoggerProvider": { "LogLevel": { "Default": "Warning", + "System.Net.Http": "None", "Microsoft.EntityFrameworkCore.Database.Command": "Information" }, "IncludeScopes": true