diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json index da9ec0bbe6..37633ca753 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json @@ -478,7 +478,8 @@ "condition": "(appInsights != true)", "exclude": [ "src/Client/Boilerplate.Client.Maui/Services/MauiTelemetryInitializer.cs", - "src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs" + "src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs", + "src/Client/Boilerplate.Client.Core/Services/AppInsightsJsSdkService.cs" ] }, { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs index a2cf42200c..ceb466d4ad 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs @@ -3,6 +3,9 @@ //#if (signalr == true) using Microsoft.AspNetCore.SignalR.Client; //#endif +//#if (appInsights == true) +using BlazorApplicationInsights.Interfaces; +//#endif namespace Boilerplate.Client.Core.Components; @@ -15,6 +18,9 @@ public partial class AppInitializer : AppComponentBase //#if (notification == true) [AutoInject] private IPushNotificationService pushNotificationService = default!; //#endif + //#if (appInsights == true) + [AutoInject] private IApplicationInsights appInsights = default!; + //#endif [AutoInject] private Navigator navigator = default!; [AutoInject] private IJSRuntime jsRuntime = default!; [AutoInject] private Bit.Butil.Console console = default!; @@ -29,7 +35,30 @@ protected override async Task OnInitAsync() { AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged; - AuthenticationStateChanged(AuthenticationManager.GetAuthenticationStateAsync()); + if (InPrerenderSession is false) + { + TelemetryContext.UserAgent = await navigator.GetUserAgent(); + TelemetryContext.TimeZone = await jsRuntime.GetTimeZone(); + TelemetryContext.Culture = CultureInfo.CurrentCulture.Name; + if (AppPlatform.IsBlazorHybrid is false) + { + TelemetryContext.OS = await jsRuntime.GetBrowserPlatform(); + } + + //#if (appInsights == true) + await appInsights.AddTelemetryInitializer(new() + { + Data = new() + { + ["ai.application.ver"] = TelemetryContext.AppVersion, + ["ai.session.id"] = TelemetryContext.AppSessionId, + ["ai.device.locale"] = TelemetryContext.Culture + } + }); + //#endif + + AuthenticationStateChanged(AuthenticationManager.GetAuthenticationStateAsync()); + } if (AppPlatform.IsBlazorHybrid) { @@ -48,19 +77,6 @@ await storageService.GetItem("Culture") ?? // 2- User settings await base.OnInitAsync(); } - protected override async Task OnAfterFirstRenderAsync() - { - await base.OnAfterFirstRenderAsync(); - - TelemetryContext.UserAgent = await navigator.GetUserAgent(); - TelemetryContext.TimeZone = await jsRuntime.GetTimeZone(); - TelemetryContext.Culture = CultureInfo.CurrentCulture.Name; - if (AppPlatform.IsBlazorHybrid is false) - { - TelemetryContext.OS = await jsRuntime.GetBrowserPlatform(); - } - } - private async void AuthenticationStateChanged(Task task) { try @@ -69,23 +85,30 @@ private async void AuthenticationStateChanged(Task task) TelemetryContext.UserId = user.IsAuthenticated() ? user.GetUserId() : null; TelemetryContext.UserSessionId = user.IsAuthenticated() ? user.GetSessionId() : null; - using var scope = authLogger.BeginScope(TelemetryContext.ToDictionary()); + var data = TelemetryContext.ToDictionary(); + + //#if (appInsights == true) + if (user.IsAuthenticated()) { - authLogger.LogInformation("Authentication state changed."); + await appInsights.SetAuthenticatedUserContext(user.GetUserId().ToString()); } - - //#if (signalr == true) - if (InPrerenderSession is false) + else { - await ConnectSignalR(); + await appInsights.ClearAuthenticatedUserContext(); } //#endif - //#if (notification == true) - if (InPrerenderSession is false) + using var scope = authLogger.BeginScope(data); { - await pushNotificationService.RegisterDevice(CurrentCancellationToken); + authLogger.LogInformation("Authentication state changed."); } + + //#if (signalr == true) + await ConnectSignalR(); + //#endif + + //#if (notification == true) + await pushNotificationService.RegisterDevice(CurrentCancellationToken); //#endif } catch (Exception exp) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor index 6d1a267dac..0536df2943 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor @@ -7,7 +7,7 @@
- + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs index 085466e21b..2b57bfe2dc 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPage.razor.cs @@ -35,7 +35,6 @@ public partial class SignInPage : IDisposable private bool isWaiting; private bool isOtpSent; private bool requiresTwoFactor; - private EditContext editContext; private readonly SignInRequestDto model = new(); private Action unsubscribeIdentityHeaderBackLinkClicked = default!; @@ -53,8 +52,6 @@ protected override async Task OnInitAsync() model.PhoneNumber = PhoneNumberQueryString; model.DeviceInfo = telemetryContext.OS; - editContext = new EditContext(model); - if (string.IsNullOrEmpty(OtpQueryString) is false) { model.Otp = OtpQueryString; @@ -151,7 +148,17 @@ private async Task SendOtp(bool resend) { if (model.Email is null && model.PhoneNumber is null) return; - if (editContext.Validate() is false) return; + if(model.Email is not null && new EmailAddressAttribute().IsValid(model.Email) is false) + { + SnackBarService.Error(string.Format(AppStrings.EmailAddressAttribute_ValidationError, AppStrings.Email)); + return; + } + + if (model.PhoneNumber is not null && new PhoneAttribute().IsValid(model.PhoneNumber) is false) + { + SnackBarService.Error(string.Format(AppStrings.PhoneAttribute_ValidationError, AppStrings.PhoneNumber)); + return; + } try { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPanel.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPanel.razor index 61e33822c0..8b467359d9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPanel.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignIn/SignInPanel.razor @@ -39,21 +39,23 @@ - - - - @Localizer[nameof(AppStrings.Password)] - - @Localizer[nameof(AppStrings.ForgotPasswordLink)] - - - - + + + + + @Localizer[nameof(AppStrings.Password)] + + @Localizer[nameof(AppStrings.ForgotPasswordLink)] + + + + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor index 0dac6aae15..ced0d2e401 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/SignUp/SignUpPage.razor @@ -25,6 +25,15 @@ + @* + + + *@ + { - var connectionString = configuration.Get()!.ApplicationInsights?.ConnectionString; - if (string.IsNullOrEmpty(connectionString) is false) - { - x.ConnectionString = connectionString; - } + x.ConnectionString = configuration.Get()!.ApplicationInsights?.ConnectionString; }); //#endif diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppInsightsJSSdkService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppInsightsJSSdkService.cs new file mode 100644 index 0000000000..4c91476c8f --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppInsightsJSSdkService.cs @@ -0,0 +1,153 @@ +using BlazorApplicationInsights; +using BlazorApplicationInsights.Models; +using BlazorApplicationInsights.Interfaces; + +namespace Boilerplate.Client.Core.Services; + +public partial class AppInsightsJsSdkService : IApplicationInsights +{ + private readonly ApplicationInsights applicationInsights = new(); + private TaskCompletionSource? appInsightsReady; + + [AutoInject] private IJSRuntime jsRuntime = default!; + + public async Task AddTelemetryInitializer(TelemetryItem telemetryItem) + { + await EnsureReady(); + await applicationInsights.AddTelemetryInitializer(telemetryItem); + } + + public async Task ClearAuthenticatedUserContext() + { + await EnsureReady(); + await applicationInsights.ClearAuthenticatedUserContext(); + } + + public async Task Context() + { + await EnsureReady(); + return await applicationInsights.Context(); + } + + public async Task Flush() + { + await EnsureReady(); + await applicationInsights.Flush(); + } + + public CookieMgr GetCookieMgr() + { + if (appInsightsReady?.Task.IsCompleted is false) + throw new InvalidOperationException("app insights is not ready"); + + return applicationInsights.GetCookieMgr(); + } + + public void InitJSRuntime(IJSRuntime jSRuntime) + { + applicationInsights.InitJSRuntime(jSRuntime); + } + + public async Task SetAuthenticatedUserContext(string authenticatedUserId, string? accountId = null, bool? storeInCookie = null) + { + await EnsureReady(); + await applicationInsights.SetAuthenticatedUserContext(authenticatedUserId, accountId, storeInCookie); + } + + public async Task StartTrackEvent(string name) + { + await EnsureReady(); + await applicationInsights.StartTrackEvent(name); + } + + public async Task StartTrackPage(string? name = null) + { + await EnsureReady(); + await applicationInsights.StartTrackPage(name); + } + + public async Task StopTrackEvent(string name, Dictionary? properties = null, Dictionary? measurements = null) + { + await EnsureReady(); + await applicationInsights.StopTrackEvent(name, properties, measurements); + } + + public async Task StopTrackPage(string? name = null, string? url = null, Dictionary? customProperties = null, Dictionary? measurements = null) + { + await EnsureReady(); + await applicationInsights.StopTrackPage(name, url, customProperties, measurements); + } + + public async Task TrackDependencyData(DependencyTelemetry dependency) + { + await EnsureReady(); + await applicationInsights.TrackDependencyData(dependency); + } + + public async Task TrackEvent(EventTelemetry @event) + { + await EnsureReady(); + await applicationInsights.TrackEvent(@event); + } + + public async Task TrackException(ExceptionTelemetry exception) + { + await EnsureReady(); + await applicationInsights.TrackException(exception); + } + + public async Task TrackMetric(MetricTelemetry metric) + { + await EnsureReady(); + await applicationInsights.TrackMetric(metric); + } + + public async Task TrackPageView(PageViewTelemetry? pageView = null) + { + await EnsureReady(); + await applicationInsights.TrackPageView(pageView); + } + + public async Task TrackPageViewPerformance(PageViewPerformanceTelemetry pageViewPerformance) + { + await EnsureReady(); + await applicationInsights.TrackPageViewPerformance(pageViewPerformance); + } + + public async Task TrackTrace(TraceTelemetry trace) + { + await EnsureReady(); + await applicationInsights.TrackTrace(trace); + } + + public async Task UpdateCfg(Config newConfig, bool mergeExisting = true) + { + await EnsureReady(); + await applicationInsights.UpdateCfg(newConfig, mergeExisting); + } + + private Task EnsureReady() + { + async void CheckForAppInsightsReady() + { + while (true) + { + try + { + var appInsightsVersion = await jsRuntime!.InvokeAsync("eval", "window.appInsights.version"); + appInsightsReady.SetResult(); + break; + } + catch { await Task.Delay(250); } + } + } + + if (appInsightsReady is null) + { + appInsightsReady = new TaskCompletionSource(); + CheckForAppInsightsReady(); + } + + return appInsightsReady!.Task; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs index 94c2a02800..cac1f1fb44 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs @@ -1,4 +1,5 @@ //+:cnd:noEmit +using System.Reflection; using System.Diagnostics; using Microsoft.Extensions.Logging; using System.Runtime.CompilerServices; @@ -19,9 +20,6 @@ public void Handle(Exception exception, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "") { - if (IgnoreException(exception)) - return; - parameters = TelemetryContext.ToDictionary(parameters); parameters[nameof(filePath)] = filePath; @@ -35,6 +33,7 @@ protected virtual void Handle(Exception exception, Dictionary pa { var isDevEnv = AppEnvironment.IsDev(); + using (var scope = Logger.BeginScope(parameters.ToDictionary(i => i.Key, i => i.Value ?? string.Empty))) { var exceptionMessage = exception.Message; @@ -60,6 +59,20 @@ protected virtual void Handle(Exception exception, Dictionary pa } } + protected Exception UnWrapException(Exception exception) + { + if (exception is AggregateException aggregateException) + { + return aggregateException.Flatten().InnerException ?? aggregateException; + } + else if (exception is TargetInvocationException) + { + return exception.InnerException ?? exception; + } + + return exception; + } + protected bool IgnoreException(Exception exception) { return exception is TaskCanceledException; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MainPage.xaml.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MainPage.xaml.cs index 73cc9838f7..0d4b8801b4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MainPage.xaml.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MainPage.xaml.cs @@ -8,14 +8,11 @@ public MainPage(ClientMauiSettings clientMauiSettings) { InitializeComponent(); //#if (appInsights == true) - if (string.IsNullOrEmpty(clientMauiSettings.ApplicationInsights?.ConnectionString) is false) + AppWebView.RootComponents.Insert(0, new() { - AppWebView.RootComponents.Add(new() - { - ComponentType = typeof(BlazorApplicationInsights.ApplicationInsightsInit), - Selector = "head::after" - }); - } + ComponentType = typeof(BlazorApplicationInsights.ApplicationInsightsInit), + Selector = "head::after" + }); //#endif } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExceptionHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExceptionHandler.cs index 49ae350185..aba5b8504f 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExceptionHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExceptionHandler.cs @@ -11,6 +11,8 @@ public partial class MauiExceptionHandler : ExceptionHandlerBase { protected override void Handle(Exception exception, Dictionary parameters) { + exception = UnWrapException(exception); + if (IgnoreException(exception)) return; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs index bc5152d5d0..fea7c32e43 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs @@ -20,17 +20,13 @@ public static async Task Main(string[] args) { // By default, App.razor adds Routes and HeadOutlet. // The following is only required for blazor webassembly standalone. - builder.RootComponents.Add("#app-container"); builder.RootComponents.Add("head::after"); //+:cnd:noEmit //#if (appInsights == true) - var clientWebSettings = builder.Configuration.Get()!; - if (string.IsNullOrEmpty(clientWebSettings.ApplicationInsights?.ConnectionString) is false) - { - builder.RootComponents.Add("head::after"); - } + builder.RootComponents.Add(selector: "head::after"); //#endif //-:cnd:noEmit + builder.RootComponents.Add("#app-container"); } builder.ConfigureServices(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebExceptionHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebExceptionHandler.cs index 3e0d95153e..2b6f05695e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebExceptionHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebExceptionHandler.cs @@ -4,6 +4,8 @@ public partial class WebExceptionHandler : ExceptionHandlerBase { protected override void Handle(Exception exception, Dictionary parameters) { + exception = UnWrapException(exception); + if (IgnoreException(exception)) return; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/MainWindow.xaml.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/MainWindow.xaml.cs index 52406a0db7..5d3f0ecc72 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/MainWindow.xaml.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/MainWindow.xaml.cs @@ -15,15 +15,11 @@ public MainWindow() services.AddClientWindowsProjectServices(configuration); InitializeComponent(); //#if (appInsights == true) - var clientWindowsSettings = configuration.Get()!; - if (string.IsNullOrEmpty(clientWindowsSettings.ApplicationInsights?.ConnectionString) is false) + AppWebView.RootComponents.Insert(0, new() { - AppWebView.RootComponents.Add(new() - { - ComponentType = typeof(BlazorApplicationInsights.ApplicationInsightsInit), - Selector = "head::after" - }); - } + ComponentType = typeof(BlazorApplicationInsights.ApplicationInsightsInit), + Selector = "head::after" + }); //#endif AppWebView.Services = services.BuildServiceProvider(); if (CultureInfoManager.MultilingualEnabled) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsExceptionHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsExceptionHandler.cs index d611dc65ab..03c98a0444 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsExceptionHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsExceptionHandler.cs @@ -10,6 +10,8 @@ public partial class WindowsExceptionHandler : ExceptionHandlerBase { protected override void Handle(Exception exception, Dictionary parameters) { + exception = UnWrapException(exception); + if (IgnoreException(exception)) return; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor index 010b748493..5aeca858f8 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor @@ -41,11 +41,8 @@ @*#if (appInsights == true)*@ - @if (string.IsNullOrEmpty(serverWebSettings.ApplicationInsights?.ConnectionString) is false) - { - - - } + + @*#endif*@ @if (serverWebSettings.WebAppRender.PwaEnabled)