From 9a533b35ce9ef1da6995b238b6525724090e5d0d Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sat, 21 Mar 2026 15:34:18 +0100 Subject: [PATCH 1/4] - ConsoleLog - NavigateTo - Refresh --- .../src/BrowserNavigationManagerInterop.cs | 2 ++ .../Web.JS/src/Services/NavigationManager.ts | 5 +++++ .../src/Hosting/WebAssemblyHostBuilder.cs | 4 ++-- .../src/Services/WebAssemblyConsoleLogger.cs | 16 +++++++--------- .../WebAssemblyConsoleLoggerProvider.cs | 15 ++------------- .../Services/WebAssemblyNavigationManager.cs | 19 +++++++++++++------ .../PrependMessageLoggerProvider.cs | 5 ++--- .../test/testassets/BasicTestApp/Program.cs | 4 ++-- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/Components/Shared/src/BrowserNavigationManagerInterop.cs b/src/Components/Shared/src/BrowserNavigationManagerInterop.cs index a51bd5720ba2..2f7d4d5c7efd 100644 --- a/src/Components/Shared/src/BrowserNavigationManagerInterop.cs +++ b/src/Components/Shared/src/BrowserNavigationManagerInterop.cs @@ -16,6 +16,8 @@ internal static class BrowserNavigationManagerInterop public const string NavigateTo = Prefix + "navigateTo"; + public const string NavigateToWithArgs = Prefix + "navigateToWithArgs"; + public const string Refresh = Prefix + "refresh"; public const string SetHasLocationChangingListeners = Prefix + "setHasLocationChangingListeners"; diff --git a/src/Components/Web.JS/src/Services/NavigationManager.ts b/src/Components/Web.JS/src/Services/NavigationManager.ts index 36f3e88e2902..57267d2040c9 100644 --- a/src/Components/Web.JS/src/Services/NavigationManager.ts +++ b/src/Components/Web.JS/src/Services/NavigationManager.ts @@ -32,6 +32,7 @@ export const internalFunctions = { setHasLocationChangingListeners, endLocationChanging, navigateTo: navigateToFromDotNet, + navigateToWithArgs: navigateToFromDotNetWithArgs, refresh, getBaseURI: (): string => document.baseURI, getLocationHref: (): string => location.href, @@ -115,6 +116,10 @@ function navigateToFromDotNet(uri: string, options: NavigationOptions): void { navigateToCore(uri, options, /* skipLocationChangingCallback */ true); } +function navigateToFromDotNetWithArgs(uri: string, forceLoad: boolean, replaceHistoryEntry: boolean, historyEntryState: string | null): void { + navigateToCore(uri, { forceLoad, replaceHistoryEntry, historyEntryState: historyEntryState ?? undefined }, /* skipLocationChangingCallback */ true); +} + function navigateToCore(uri: string, options: NavigationOptions, skipLocationChangingCallback = false): void { const absoluteUri = toAbsoluteUri(uri); const pageLoadMechanism = currentPageLoadMechanism(); diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index 9452c6c33543..58eee4871b1c 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -341,12 +341,12 @@ internal void InitializeDefaultServices() RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Services, RenderMode.InteractiveWebAssembly); Services.AddLogging(builder => { - builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance)); + builder.AddProvider(new WebAssemblyConsoleLoggerProvider()); }); Services.AddSingleton(); RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(Services, RenderMode.InteractiveWebAssembly); Services.AddSupplyValueFromQueryProvider(); - + // Register metrics and tracing when explicitly enabled (opt-in via feature switch) var isTelemetryEnabled = AppContext.TryGetSwitch("System.Diagnostics.Metrics.Meter.IsSupported", out var switchValue) && switchValue == true; if (isTelemetryEnabled) diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs index c2328cc47dc2..186969184e67 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs @@ -5,8 +5,6 @@ using System.Runtime.InteropServices.JavaScript; using System.Text; using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using Microsoft.JSInterop.WebAssembly; namespace Microsoft.AspNetCore.Components.WebAssembly.Services; @@ -18,17 +16,15 @@ internal sealed class WebAssemblyConsoleLogger : ILogger, ILogger private static readonly StringBuilder _logBuilder = new StringBuilder(); private readonly string _name; - private readonly WebAssemblyJSRuntime _jsRuntime; - public WebAssemblyConsoleLogger(IJSRuntime jsRuntime) - : this(string.Empty, (WebAssemblyJSRuntime)jsRuntime) // Cast for DI + public WebAssemblyConsoleLogger() + : this(string.Empty) { } - public WebAssemblyConsoleLogger(string name, WebAssemblyJSRuntime jsRuntime) + public WebAssemblyConsoleLogger(string name) { _name = name ?? throw new ArgumentNullException(nameof(name)); - _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); } public IDisposable? BeginScope(TState state) where TState : notnull @@ -58,7 +54,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } } - private void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception? exception) + private static void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception? exception) { lock (_logBuilder) { @@ -93,7 +89,7 @@ private void WriteMessage(LogLevel logLevel, string logName, int eventId, string break; default: // invalid enum values Debug.Assert(logLevel != LogLevel.None, "This method is never called with LogLevel.None."); - _jsRuntime.InvokeVoid("console.log", formattedMessage); + ConsoleLoggerInterop.ConsoleLog(formattedMessage); break; } } @@ -166,6 +162,8 @@ public void Dispose() { } internal static partial class ConsoleLoggerInterop { + [JSImport("globalThis.console.log")] + public static partial void ConsoleLog(string message); [JSImport("globalThis.console.debug")] public static partial void ConsoleDebug(string message); [JSImport("globalThis.console.info")] diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs index 6d4c62213ab0..aa2ccc3203e7 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using Microsoft.Extensions.Logging; -using Microsoft.JSInterop.WebAssembly; namespace Microsoft.AspNetCore.Components.WebAssembly.Services; @@ -12,22 +11,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services; /// internal sealed class WebAssemblyConsoleLoggerProvider : ILoggerProvider { - private readonly ConcurrentDictionary> _loggers; - private readonly WebAssemblyJSRuntime _jsRuntime; - - /// - /// Creates an instance of . - /// - public WebAssemblyConsoleLoggerProvider(WebAssemblyJSRuntime jsRuntime) - { - _loggers = new ConcurrentDictionary>(); - _jsRuntime = jsRuntime; - } + private readonly ConcurrentDictionary> _loggers = new(); /// public ILogger CreateLogger(string name) { - return _loggers.GetOrAdd(name, loggerName => new WebAssemblyConsoleLogger(name, _jsRuntime)); + return _loggers.GetOrAdd(name, loggerName => new WebAssemblyConsoleLogger(name)); } /// diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs index 401938dbe076..461a0604c2e3 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs @@ -1,11 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices.JavaScript; using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop; namespace Microsoft.AspNetCore.Components.WebAssembly.Services; @@ -49,7 +48,6 @@ public async ValueTask HandleLocationChangingAsync(string uri, string? sta } /// - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(NavigationOptions))] protected override void NavigateToCore(string uri, NavigationOptions options) { ArgumentNullException.ThrowIfNull(uri); @@ -68,7 +66,7 @@ async Task PerformNavigationAsync() return; } - DefaultWebAssemblyJSRuntime.Instance.InvokeVoid(Interop.NavigateTo, uri, options); + NavigationManagerInterop.NavigateTo(uri, options.ForceLoad, options.ReplaceHistoryEntry, options.HistoryEntryState); } catch (Exception ex) { @@ -82,7 +80,7 @@ async Task PerformNavigationAsync() /// public override void Refresh(bool forceReload = false) { - DefaultWebAssemblyJSRuntime.Instance.InvokeVoid(Interop.Refresh, forceReload); + NavigationManagerInterop.Refresh(forceReload); } protected override void HandleLocationChangingHandlerException(Exception ex, LocationChangingContext context) @@ -102,3 +100,12 @@ private static partial class Log public static partial void NavigationFailed(ILogger logger, string uri, Exception exception); } } + +internal static partial class NavigationManagerInterop +{ + [JSImport(BrowserNavigationManagerInterop.NavigateToWithArgs, "blazor-internal")] + public static partial void NavigateTo(string uri, bool forceLoad, bool replaceHistoryEntry, string? historyEntryState); + + [JSImport(BrowserNavigationManagerInterop.Refresh, "blazor-internal")] + public static partial void Refresh(bool forceReload); +} diff --git a/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs b/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs index e2353a827484..5acdae8626f6 100644 --- a/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs +++ b/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.WebAssembly.Services; -using Microsoft.JSInterop; namespace BasicTestApp; @@ -14,10 +13,10 @@ internal class PrependMessageLoggerProvider : ILoggerProvider readonly ILogger _defaultLogger; private bool _disposed = false; - public PrependMessageLoggerProvider(string message, IJSRuntime runtime) + public PrependMessageLoggerProvider(string message) { _message = message; - _defaultLogger = new WebAssemblyConsoleLogger(runtime); + _defaultLogger = new WebAssemblyConsoleLogger(); } public ILogger CreateLogger(string categoryName) diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index 0332b3915c88..ed788f5e62fe 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -50,8 +50,8 @@ public static async Task Main(string[] args) builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); - builder.Logging.Services.AddSingleton(s => - new PrependMessageLoggerProvider(builder.Configuration["Logging:PrependMessage:Message"], s.GetService())); + builder.Logging.Services.AddSingleton(_ => + new PrependMessageLoggerProvider(builder.Configuration["Logging:PrependMessage:Message"])); var host = builder.Build(); ConfigureCulture(host); From 9e2adab97c7aea752955d9d50ecbf8135cd5ad2d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Sat, 21 Mar 2026 15:49:56 +0100 Subject: [PATCH 2/4] Apply suggestion from @campersau Co-authored-by: campersau --- .../src/Services/WebAssemblyConsoleLoggerProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs index aa2ccc3203e7..75f541bca610 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs @@ -16,7 +16,7 @@ internal sealed class WebAssemblyConsoleLoggerProvider : ILoggerProvider /// public ILogger CreateLogger(string name) { - return _loggers.GetOrAdd(name, loggerName => new WebAssemblyConsoleLogger(name)); + return _loggers.GetOrAdd(name, static loggerName => new WebAssemblyConsoleLogger(loggerName)); } /// From 0db89dc6083ab8df123cdb4419929b7e2a80b7e3 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sat, 21 Mar 2026 16:17:57 +0100 Subject: [PATCH 3/4] - NotifyLocationChanged - NotifyLocationChangingAsync --- .../Web.JS/src/Boot.WebAssembly.Common.ts | 18 +++--------------- src/Components/Web.JS/src/GlobalExports.ts | 2 ++ .../Services/DefaultWebAssemblyJSRuntime.cs | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts index 46311e7f1c8f..18995332cadc 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts @@ -121,22 +121,10 @@ async function startCore(components: RootComponentManager => { - await dispatcher.invokeDotNetStaticMethodAsync( - 'Microsoft.AspNetCore.Components.WebAssembly', - 'NotifyLocationChanged', - uri, - state, - intercepted - ); + Blazor._internal.navigationManager.listenForNavigationEvents(WebRendererId.WebAssembly, (uri: string, state: string | undefined, intercepted: boolean): void => { + Blazor._internal.dotNetExports!.NotifyLocationChanged(uri, state ?? null, intercepted); }, async (callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise => { - const shouldContinueNavigation = await dispatcher.invokeDotNetStaticMethodAsync( - 'Microsoft.AspNetCore.Components.WebAssembly', - 'NotifyLocationChangingAsync', - uri, - state, - intercepted - ); + const shouldContinueNavigation = await Blazor._internal.dotNetExports!.NotifyLocationChangingAsync(uri, state ?? null, intercepted); Blazor._internal.navigationManager.endLocationChanging(callId, shouldContinueNavigation); }); diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 6bd2fbe69c75..91cedc1baf38 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -93,6 +93,8 @@ export interface IBlazor { BeginInvokeDotNet: (callId: string | null, assemblyNameOrDotNetObjectId: string, methodIdentifier: string, argsJson: string) => void; ReceiveByteArrayFromJS: (id: number, data: Uint8Array) => void; UpdateRootComponentsCore: (operationsJson: string, appState: string) => void; + NotifyLocationChanged: (uri: string, state: string | null, isInterceptedLink: boolean) => void; + NotifyLocationChangingAsync: (uri: string, state: string | null, isInterceptedLink: boolean) => Promise; } // APIs invoked by hot reload diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index f55739f2bdda..f562ca1351fd 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -31,6 +31,8 @@ internal sealed partial class DefaultWebAssemblyJSRuntime : WebAssemblyJSRuntime [DynamicDependency(nameof(BeginInvokeDotNet))] [DynamicDependency(nameof(ReceiveByteArrayFromJS))] [DynamicDependency(nameof(UpdateRootComponentsCore))] + [DynamicDependency(nameof(NotifyLocationChanged))] + [DynamicDependency(nameof(NotifyLocationChangingAsync))] [DynamicDependency(JsonSerialized, typeof(KeyValuePair<,>))] private DefaultWebAssemblyJSRuntime() { @@ -93,6 +95,21 @@ public static void BeginInvokeDotNet(string? callId, string assemblyNameOrDotNet }); } + [JSExport] + [SupportedOSPlatform("browser")] + public static void NotifyLocationChanged(string uri, string? state, bool isInterceptedLink) + { + WebAssemblyNavigationManager.Instance.SetLocation(uri, state, isInterceptedLink); + } + + [JSExport] + [SupportedOSPlatform("browser")] + [return: JSMarshalAs>] + public static async Task NotifyLocationChangingAsync(string uri, string? state, bool isInterceptedLink) + { + return await WebAssemblyNavigationManager.Instance.HandleLocationChangingAsync(uri, state, isInterceptedLink); + } + [SupportedOSPlatform("browser")] [JSExport] public static void UpdateRootComponentsCore(string operationsJson, string appState) From f0f5cfc936d29500cc806b37fea2d355dc6e78d4 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 22 Mar 2026 07:53:36 +0100 Subject: [PATCH 4/4] Revert "- NotifyLocationChanged" This reverts commit 0db89dc6083ab8df123cdb4419929b7e2a80b7e3. --- .../Web.JS/src/Boot.WebAssembly.Common.ts | 18 +++++++++++++++--- src/Components/Web.JS/src/GlobalExports.ts | 2 -- .../Services/DefaultWebAssemblyJSRuntime.cs | 17 ----------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts index 18995332cadc..46311e7f1c8f 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts @@ -121,10 +121,22 @@ async function startCore(components: RootComponentManager { - Blazor._internal.dotNetExports!.NotifyLocationChanged(uri, state ?? null, intercepted); + Blazor._internal.navigationManager.listenForNavigationEvents(WebRendererId.WebAssembly, async (uri: string, state: string | undefined, intercepted: boolean): Promise => { + await dispatcher.invokeDotNetStaticMethodAsync( + 'Microsoft.AspNetCore.Components.WebAssembly', + 'NotifyLocationChanged', + uri, + state, + intercepted + ); }, async (callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise => { - const shouldContinueNavigation = await Blazor._internal.dotNetExports!.NotifyLocationChangingAsync(uri, state ?? null, intercepted); + const shouldContinueNavigation = await dispatcher.invokeDotNetStaticMethodAsync( + 'Microsoft.AspNetCore.Components.WebAssembly', + 'NotifyLocationChangingAsync', + uri, + state, + intercepted + ); Blazor._internal.navigationManager.endLocationChanging(callId, shouldContinueNavigation); }); diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 91cedc1baf38..6bd2fbe69c75 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -93,8 +93,6 @@ export interface IBlazor { BeginInvokeDotNet: (callId: string | null, assemblyNameOrDotNetObjectId: string, methodIdentifier: string, argsJson: string) => void; ReceiveByteArrayFromJS: (id: number, data: Uint8Array) => void; UpdateRootComponentsCore: (operationsJson: string, appState: string) => void; - NotifyLocationChanged: (uri: string, state: string | null, isInterceptedLink: boolean) => void; - NotifyLocationChangingAsync: (uri: string, state: string | null, isInterceptedLink: boolean) => Promise; } // APIs invoked by hot reload diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index f562ca1351fd..f55739f2bdda 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -31,8 +31,6 @@ internal sealed partial class DefaultWebAssemblyJSRuntime : WebAssemblyJSRuntime [DynamicDependency(nameof(BeginInvokeDotNet))] [DynamicDependency(nameof(ReceiveByteArrayFromJS))] [DynamicDependency(nameof(UpdateRootComponentsCore))] - [DynamicDependency(nameof(NotifyLocationChanged))] - [DynamicDependency(nameof(NotifyLocationChangingAsync))] [DynamicDependency(JsonSerialized, typeof(KeyValuePair<,>))] private DefaultWebAssemblyJSRuntime() { @@ -95,21 +93,6 @@ public static void BeginInvokeDotNet(string? callId, string assemblyNameOrDotNet }); } - [JSExport] - [SupportedOSPlatform("browser")] - public static void NotifyLocationChanged(string uri, string? state, bool isInterceptedLink) - { - WebAssemblyNavigationManager.Instance.SetLocation(uri, state, isInterceptedLink); - } - - [JSExport] - [SupportedOSPlatform("browser")] - [return: JSMarshalAs>] - public static async Task NotifyLocationChangingAsync(string uri, string? state, bool isInterceptedLink) - { - return await WebAssemblyNavigationManager.Instance.HandleLocationChangingAsync(uri, state, isInterceptedLink); - } - [SupportedOSPlatform("browser")] [JSExport] public static void UpdateRootComponentsCore(string operationsJson, string appState)