From 6ce2041c863fa8f43f5078429e8ff441743627b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 09:07:37 +0000 Subject: [PATCH 1/3] Initial plan From c76fad45aa5a3fecae39dab9e2a2a5f1a5cae402 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 09:22:18 +0000 Subject: [PATCH 2/3] Address BrowserConfiguration API review feedback (rename to BrowserOptions, reshape API, preserve wire format) Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../BrowserConfiguration.cs | 26 ------- .../BrowserConfiguration/BrowserOptions.cs | 29 +++++++ ....cs => InteractiveServerBrowserOptions.cs} | 12 +-- .../BrowserConfiguration/SsrBrowserOptions.cs | 9 ++- .../WebAssemblyBrowserOptions.cs | 8 +- .../Components/src/PublicAPI.Unshipped.txt | 44 +++++------ ...=> BrowserOptionsHttpContextExtensions.cs} | 22 +++--- .../BrowserConfiguration/ConfigureBrowser.cs | 20 ++--- ...entsEndpointConventionBuilderExtensions.cs | 16 ++-- .../Endpoints/src/PublicAPI.Unshipped.txt | 10 +-- .../BrowserConfigurationJsonContext.cs | 4 +- .../BrowserConfigurationWireModel.cs | 77 +++++++++++++++++++ .../EndpointHtmlRenderer.Streaming.cs | 5 +- .../RazorComponentEndpointsStartup.cs | 2 +- 14 files changed, 182 insertions(+), 102 deletions(-) delete mode 100644 src/Components/Components/src/BrowserConfiguration/BrowserConfiguration.cs create mode 100644 src/Components/Components/src/BrowserConfiguration/BrowserOptions.cs rename src/Components/Components/src/BrowserConfiguration/{ServerBrowserOptions.cs => InteractiveServerBrowserOptions.cs} (67%) rename src/Components/Endpoints/src/BrowserConfiguration/{BrowserConfigurationHttpContextExtensions.cs => BrowserOptionsHttpContextExtensions.cs} (56%) create mode 100644 src/Components/Endpoints/src/Rendering/BrowserConfigurationWireModel.cs diff --git a/src/Components/Components/src/BrowserConfiguration/BrowserConfiguration.cs b/src/Components/Components/src/BrowserConfiguration/BrowserConfiguration.cs deleted file mode 100644 index 8883f202376b..000000000000 --- a/src/Components/Components/src/BrowserConfiguration/BrowserConfiguration.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Components; - -/// -/// Configuration that flows from the server to the Blazor client in the browser -/// via a DOM comment. Only serializable options; callbacks stay with JS initializers. -/// -public sealed class BrowserConfiguration -{ - /// - /// The log level for the Blazor JS runtime. Applies to all render modes. - /// Maps to WebStartOptions.logLevel. - /// - public int? LogLevel { get; set; } - - /// WebAssembly-specific options. - public WebAssemblyBrowserOptions WebAssembly { get; set; } = new(); - - /// Server/Circuit-specific options. - public ServerBrowserOptions Server { get; set; } = new(); - - /// SSR-specific options. - public SsrBrowserOptions Ssr { get; set; } = new(); -} diff --git a/src/Components/Components/src/BrowserConfiguration/BrowserOptions.cs b/src/Components/Components/src/BrowserConfiguration/BrowserOptions.cs new file mode 100644 index 000000000000..000283997b6c --- /dev/null +++ b/src/Components/Components/src/BrowserConfiguration/BrowserOptions.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Components; + +/// +/// Options that flow from the server to the Blazor client in the browser +/// via a DOM comment. Only serializable options are included; callbacks stay +/// with JS initializers. +/// +public sealed class BrowserOptions +{ + /// + /// Gets or sets the log level for the Blazor JS runtime. Applies to all render modes. + /// Maps to WebStartOptions.logLevel. + /// + public LogLevel? LogLevel { get; set; } + + /// Gets the WebAssembly-specific options. + public WebAssemblyBrowserOptions WebAssembly { get; } = new(); + + /// Gets the interactive server (circuit) specific options. + public InteractiveServerBrowserOptions Server { get; } = new(); + + /// Gets the SSR-specific options. + public SsrBrowserOptions Ssr { get; } = new(); +} diff --git a/src/Components/Components/src/BrowserConfiguration/ServerBrowserOptions.cs b/src/Components/Components/src/BrowserConfiguration/InteractiveServerBrowserOptions.cs similarity index 67% rename from src/Components/Components/src/BrowserConfiguration/ServerBrowserOptions.cs rename to src/Components/Components/src/BrowserConfiguration/InteractiveServerBrowserOptions.cs index b8aee08f950e..b61e7f26567f 100644 --- a/src/Components/Components/src/BrowserConfiguration/ServerBrowserOptions.cs +++ b/src/Components/Components/src/BrowserConfiguration/InteractiveServerBrowserOptions.cs @@ -4,27 +4,27 @@ namespace Microsoft.AspNetCore.Components; /// -/// Serializable subset of CircuitStartOptions. +/// Serializable subset of CircuitStartOptions for the interactive server render mode. /// Non-serializable options (configureSignalR, reconnectionHandler, /// circuitHandlers) must use Blazor.start() or JS initializers. /// -public sealed class ServerBrowserOptions +public sealed class InteractiveServerBrowserOptions { /// - /// Maximum reconnection attempts before giving up. + /// Gets or sets the maximum reconnection attempts before giving up. /// Maps to CircuitStartOptions.reconnectionOptions.maxRetries. /// public int? ReconnectionMaxRetries { get; set; } /// - /// Base interval in milliseconds between reconnection attempts (scalar form). + /// Gets or sets the base interval between reconnection attempts (scalar form). /// The function form (retryCount, currentMs) => number requires JS. /// Maps to CircuitStartOptions.reconnectionOptions.retryIntervalMilliseconds. /// - public int? ReconnectionRetryIntervalMilliseconds { get; set; } + public TimeSpan? ReconnectionRetryInterval { get; set; } /// - /// CSS ID of the reconnection dialog element. + /// Gets or sets the CSS ID of the reconnection dialog element. /// Maps to CircuitStartOptions.reconnectionOptions.dialogId. /// public string? ReconnectionDialogId { get; set; } diff --git a/src/Components/Components/src/BrowserConfiguration/SsrBrowserOptions.cs b/src/Components/Components/src/BrowserConfiguration/SsrBrowserOptions.cs index dfd78a0b0ae9..bac9b333ee8a 100644 --- a/src/Components/Components/src/BrowserConfiguration/SsrBrowserOptions.cs +++ b/src/Components/Components/src/BrowserConfiguration/SsrBrowserOptions.cs @@ -9,14 +9,15 @@ namespace Microsoft.AspNetCore.Components; public sealed class SsrBrowserOptions { /// - /// When true, disables DOM preservation during enhanced navigation. + /// Gets or sets a value indicating whether the DOM is preserved during enhanced navigation. + /// When , DOM preservation is disabled. leaves the value unset. /// Maps to SsrStartOptions.disableDomPreservation. /// - public bool? DisableDomPreservation { get; set; } + public bool? PreserveDom { get; set; } /// - /// Timeout in milliseconds before an inactive circuit is disposed. + /// Gets or sets the timeout before an inactive circuit is disposed. /// Maps to SsrStartOptions.circuitInactivityTimeoutMs. /// - public int? CircuitInactivityTimeoutMs { get; set; } + public TimeSpan? CircuitInactivityTimeout { get; set; } } diff --git a/src/Components/Components/src/BrowserConfiguration/WebAssemblyBrowserOptions.cs b/src/Components/Components/src/BrowserConfiguration/WebAssemblyBrowserOptions.cs index 6eeccd428b2b..fbcef2476909 100644 --- a/src/Components/Components/src/BrowserConfiguration/WebAssemblyBrowserOptions.cs +++ b/src/Components/Components/src/BrowserConfiguration/WebAssemblyBrowserOptions.cs @@ -11,20 +11,20 @@ namespace Microsoft.AspNetCore.Components; public sealed class WebAssemblyBrowserOptions { /// - /// Hosting environment name (e.g., "Development", "Production"). + /// Gets or sets the hosting environment name (e.g., "Development", "Production"). /// Maps to WebAssemblyStartOptions.environment. /// public string? EnvironmentName { get; set; } /// - /// Application culture in BCP 47 format (e.g., "en-US"). + /// Gets or sets the application culture in BCP 47 format (e.g., "en-US"). /// Maps to WebAssemblyStartOptions.applicationCulture. /// public string? ApplicationCulture { get; set; } /// - /// Environment variables for the .NET WebAssembly runtime. + /// Gets the environment variables for the .NET WebAssembly runtime. /// Use for OTEL endpoints, service URLs, etc. /// - public Dictionary EnvironmentVariables { get; set; } = new(); + public IDictionary EnvironmentVariables { get; } = new Dictionary(); } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 1860c583402f..ffb60e308c9a 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -13,35 +13,31 @@ Microsoft.AspNetCore.Components.IComponentPropertyActivator.GetActivator(System. *REMOVED*Microsoft.AspNetCore.Components.Routing.Router.PreferExactMatches.get -> bool *REMOVED*Microsoft.AspNetCore.Components.Routing.Router.PreferExactMatches.set -> void static Microsoft.Extensions.DependencyInjection.CascadingValueServiceCollectionExtensions.TryAddCascadingValueSupplier(this Microsoft.Extensions.DependencyInjection.IServiceCollection! serviceCollection, System.Func!>! subscribeFactory) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -Microsoft.AspNetCore.Components.BrowserConfiguration -Microsoft.AspNetCore.Components.BrowserConfiguration.BrowserConfiguration() -> void -Microsoft.AspNetCore.Components.BrowserConfiguration.LogLevel.get -> int? -Microsoft.AspNetCore.Components.BrowserConfiguration.LogLevel.set -> void -Microsoft.AspNetCore.Components.BrowserConfiguration.Server.get -> Microsoft.AspNetCore.Components.ServerBrowserOptions! -Microsoft.AspNetCore.Components.BrowserConfiguration.Server.set -> void -Microsoft.AspNetCore.Components.BrowserConfiguration.Ssr.get -> Microsoft.AspNetCore.Components.SsrBrowserOptions! -Microsoft.AspNetCore.Components.BrowserConfiguration.Ssr.set -> void -Microsoft.AspNetCore.Components.BrowserConfiguration.WebAssembly.get -> Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions! -Microsoft.AspNetCore.Components.BrowserConfiguration.WebAssembly.set -> void -Microsoft.AspNetCore.Components.ServerBrowserOptions -Microsoft.AspNetCore.Components.ServerBrowserOptions.ServerBrowserOptions() -> void -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionDialogId.get -> string? -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionDialogId.set -> void -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionMaxRetries.get -> int? -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionMaxRetries.set -> void -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionRetryIntervalMilliseconds.get -> int? -Microsoft.AspNetCore.Components.ServerBrowserOptions.ReconnectionRetryIntervalMilliseconds.set -> void +Microsoft.AspNetCore.Components.BrowserOptions +Microsoft.AspNetCore.Components.BrowserOptions.BrowserOptions() -> void +Microsoft.AspNetCore.Components.BrowserOptions.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel? +Microsoft.AspNetCore.Components.BrowserOptions.LogLevel.set -> void +Microsoft.AspNetCore.Components.BrowserOptions.Server.get -> Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions! +Microsoft.AspNetCore.Components.BrowserOptions.Ssr.get -> Microsoft.AspNetCore.Components.SsrBrowserOptions! +Microsoft.AspNetCore.Components.BrowserOptions.WebAssembly.get -> Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions! +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.InteractiveServerBrowserOptions() -> void +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionDialogId.get -> string? +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionDialogId.set -> void +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionMaxRetries.get -> int? +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionMaxRetries.set -> void +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionRetryInterval.get -> System.TimeSpan? +Microsoft.AspNetCore.Components.InteractiveServerBrowserOptions.ReconnectionRetryInterval.set -> void Microsoft.AspNetCore.Components.SsrBrowserOptions Microsoft.AspNetCore.Components.SsrBrowserOptions.SsrBrowserOptions() -> void -Microsoft.AspNetCore.Components.SsrBrowserOptions.CircuitInactivityTimeoutMs.get -> int? -Microsoft.AspNetCore.Components.SsrBrowserOptions.CircuitInactivityTimeoutMs.set -> void -Microsoft.AspNetCore.Components.SsrBrowserOptions.DisableDomPreservation.get -> bool? -Microsoft.AspNetCore.Components.SsrBrowserOptions.DisableDomPreservation.set -> void +Microsoft.AspNetCore.Components.SsrBrowserOptions.CircuitInactivityTimeout.get -> System.TimeSpan? +Microsoft.AspNetCore.Components.SsrBrowserOptions.CircuitInactivityTimeout.set -> void +Microsoft.AspNetCore.Components.SsrBrowserOptions.PreserveDom.get -> bool? +Microsoft.AspNetCore.Components.SsrBrowserOptions.PreserveDom.set -> void Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.WebAssemblyBrowserOptions() -> void Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.ApplicationCulture.get -> string? Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.ApplicationCulture.set -> void Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.EnvironmentName.get -> string? Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.EnvironmentName.set -> void -Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.EnvironmentVariables.get -> System.Collections.Generic.Dictionary! -Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.EnvironmentVariables.set -> void +Microsoft.AspNetCore.Components.WebAssemblyBrowserOptions.EnvironmentVariables.get -> System.Collections.Generic.IDictionary! diff --git a/src/Components/Endpoints/src/BrowserConfiguration/BrowserConfigurationHttpContextExtensions.cs b/src/Components/Endpoints/src/BrowserConfiguration/BrowserOptionsHttpContextExtensions.cs similarity index 56% rename from src/Components/Endpoints/src/BrowserConfiguration/BrowserConfigurationHttpContextExtensions.cs rename to src/Components/Endpoints/src/BrowserConfiguration/BrowserOptionsHttpContextExtensions.cs index a5d1dc07a145..bd41b162f3e8 100644 --- a/src/Components/Endpoints/src/BrowserConfiguration/BrowserConfigurationHttpContextExtensions.cs +++ b/src/Components/Endpoints/src/BrowserConfiguration/BrowserOptionsHttpContextExtensions.cs @@ -1,34 +1,36 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.Http; +namespace Microsoft.AspNetCore.Components; /// -/// Extension methods on for accessing . +/// Extension methods on for accessing . /// -public static class BrowserConfigurationHttpContextExtensions +public static class BrowserOptionsHttpContextExtensions { private static readonly object Key = new(); /// - /// Gets the for the current request. + /// Gets the for the current request. /// If not already set, seeds from endpoint metadata or creates a new instance. /// /// The . - /// The for the current request. - public static BrowserConfiguration GetBrowserConfiguration(this HttpContext context) + /// The for the current request. + public static BrowserOptions GetBrowserOptions(this HttpContext context) { + ArgumentNullException.ThrowIfNull(context); + if (!context.Items.TryGetValue(Key, out var result)) { // Seed from endpoint metadata if available - var metadataConfig = context.GetEndpoint()?.Metadata.GetMetadata(); - var config = metadataConfig ?? new BrowserConfiguration(); + var metadataConfig = context.GetEndpoint()?.Metadata.GetMetadata(); + var config = metadataConfig ?? new BrowserOptions(); context.Items[Key] = config; return config; } - return (BrowserConfiguration)result!; + return (BrowserOptions)result!; } } diff --git a/src/Components/Endpoints/src/BrowserConfiguration/ConfigureBrowser.cs b/src/Components/Endpoints/src/BrowserConfiguration/ConfigureBrowser.cs index 5d8fac5c1c8a..cbd66495e8d9 100644 --- a/src/Components/Endpoints/src/BrowserConfiguration/ConfigureBrowser.cs +++ b/src/Components/Endpoints/src/BrowserConfiguration/ConfigureBrowser.cs @@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Components; /// /// A component that configures the Blazor browser runtime by merging -/// configuration into the on the current -/// . The merged configuration is emitted as +/// options into the on the current +/// . The merged options are emitted as /// a <!--Blazor-Configuration:{...}--> DOM comment by the renderer. /// public sealed class ConfigureBrowser : IComponent @@ -16,10 +16,10 @@ public sealed class ConfigureBrowser : IComponent private RenderHandle _renderHandle; /// - /// Gets or sets the to merge. + /// Gets or sets the to merge. /// [Parameter, EditorRequired] - public BrowserConfiguration Configuration { get; set; } = default!; + public BrowserOptions Options { get; set; } = default!; /// /// Gets or sets the for the current request. @@ -38,14 +38,14 @@ Task IComponent.SetParametersAsync(ParameterView parameters) if (HttpContext is not null) { - var existing = HttpContext.GetBrowserConfiguration(); - MergeInto(existing, Configuration); + var existing = HttpContext.GetBrowserOptions(); + MergeInto(existing, Options); } return Task.CompletedTask; } - internal static void MergeInto(BrowserConfiguration target, BrowserConfiguration source) + internal static void MergeInto(BrowserOptions target, BrowserOptions source) { target.LogLevel = source.LogLevel ?? target.LogLevel; @@ -59,11 +59,11 @@ internal static void MergeInto(BrowserConfiguration target, BrowserConfiguration // Server target.Server.ReconnectionMaxRetries = source.Server.ReconnectionMaxRetries ?? target.Server.ReconnectionMaxRetries; - target.Server.ReconnectionRetryIntervalMilliseconds = source.Server.ReconnectionRetryIntervalMilliseconds ?? target.Server.ReconnectionRetryIntervalMilliseconds; + target.Server.ReconnectionRetryInterval = source.Server.ReconnectionRetryInterval ?? target.Server.ReconnectionRetryInterval; target.Server.ReconnectionDialogId = source.Server.ReconnectionDialogId ?? target.Server.ReconnectionDialogId; // SSR - target.Ssr.DisableDomPreservation = source.Ssr.DisableDomPreservation ?? target.Ssr.DisableDomPreservation; - target.Ssr.CircuitInactivityTimeoutMs = source.Ssr.CircuitInactivityTimeoutMs ?? target.Ssr.CircuitInactivityTimeoutMs; + target.Ssr.PreserveDom = source.Ssr.PreserveDom ?? target.Ssr.PreserveDom; + target.Ssr.CircuitInactivityTimeout = source.Ssr.CircuitInactivityTimeout ?? target.Ssr.CircuitInactivityTimeout; } } diff --git a/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs b/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs index 754663d2cd4b..79c64f0d646d 100644 --- a/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs +++ b/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs @@ -65,25 +65,25 @@ public static RazorComponentsEndpointConventionBuilder WithStaticAssets( } /// - /// Configures a that will be emitted as a DOM comment + /// Configures a that will be emitted as a DOM comment /// to the browser for all Razor component endpoints. /// /// The . - /// An action to configure the . + /// An action to configure the . /// The . - public static RazorComponentsEndpointConventionBuilder WithBrowserConfiguration( + public static RazorComponentsEndpointConventionBuilder WithBrowserOptions( this RazorComponentsEndpointConventionBuilder builder, - Action configure) + Action configureOptions) { ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(configure); + ArgumentNullException.ThrowIfNull(configureOptions); - var config = new BrowserConfiguration(); - configure(config); + var options = new BrowserOptions(); + configureOptions(options); builder.Add(endpointBuilder => { - endpointBuilder.Metadata.Add(config); + endpointBuilder.Metadata.Add(options); }); return builder; diff --git a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt index e26fbfcce577..82f2207edc20 100644 --- a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt +++ b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt @@ -15,10 +15,10 @@ Microsoft.AspNetCore.Components.ITempData.Keep(string! key) -> void Microsoft.AspNetCore.Components.ITempData.Peek(string! key) -> object? Microsoft.AspNetCore.Components.ConfigureBrowser Microsoft.AspNetCore.Components.ConfigureBrowser.ConfigureBrowser() -> void -Microsoft.AspNetCore.Components.ConfigureBrowser.Configuration.get -> Microsoft.AspNetCore.Components.BrowserConfiguration! -Microsoft.AspNetCore.Components.ConfigureBrowser.Configuration.set -> void +Microsoft.AspNetCore.Components.ConfigureBrowser.Options.get -> Microsoft.AspNetCore.Components.BrowserOptions! +Microsoft.AspNetCore.Components.ConfigureBrowser.Options.set -> void Microsoft.AspNetCore.Components.ConfigureBrowser.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext? Microsoft.AspNetCore.Components.ConfigureBrowser.HttpContext.set -> void -Microsoft.AspNetCore.Http.BrowserConfigurationHttpContextExtensions -static Microsoft.AspNetCore.Http.BrowserConfigurationHttpContextExtensions.GetBrowserConfiguration(this Microsoft.AspNetCore.Http.HttpContext! context) -> Microsoft.AspNetCore.Components.BrowserConfiguration! -static Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilderExtensions.WithBrowserConfiguration(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action! configure) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! +Microsoft.AspNetCore.Components.BrowserOptionsHttpContextExtensions +static Microsoft.AspNetCore.Components.BrowserOptionsHttpContextExtensions.GetBrowserOptions(this Microsoft.AspNetCore.Http.HttpContext! context) -> Microsoft.AspNetCore.Components.BrowserOptions! +static Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilderExtensions.WithBrowserOptions(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action! configureOptions) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! diff --git a/src/Components/Endpoints/src/Rendering/BrowserConfigurationJsonContext.cs b/src/Components/Endpoints/src/Rendering/BrowserConfigurationJsonContext.cs index ace7473c1956..5bd3ec920e65 100644 --- a/src/Components/Endpoints/src/Rendering/BrowserConfigurationJsonContext.cs +++ b/src/Components/Endpoints/src/Rendering/BrowserConfigurationJsonContext.cs @@ -3,12 +3,12 @@ using System.Text.Json.Serialization; -namespace Microsoft.AspNetCore.Components; +namespace Microsoft.AspNetCore.Components.Endpoints; [JsonSourceGenerationOptions( PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(BrowserConfiguration))] +[JsonSerializable(typeof(BrowserConfigurationWireModel))] internal partial class BrowserConfigurationJsonContext : JsonSerializerContext { } diff --git a/src/Components/Endpoints/src/Rendering/BrowserConfigurationWireModel.cs b/src/Components/Endpoints/src/Rendering/BrowserConfigurationWireModel.cs new file mode 100644 index 000000000000..7540448ba93c --- /dev/null +++ b/src/Components/Endpoints/src/Rendering/BrowserConfigurationWireModel.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Components.Endpoints; + +// Internal wire representation of . The serialized format +// is consumed by the Blazor JS runtime and is treated as a versioned/internal contract, +// so it is decoupled from the public options shape. Durations are emitted as milliseconds +// and positive options (e.g. PreserveDom) are mapped to the JS negative form. +internal sealed class BrowserConfigurationWireModel +{ + public int? LogLevel { get; set; } + + public WebAssemblyWireModel WebAssembly { get; set; } = new(); + + public ServerWireModel Server { get; set; } = new(); + + public SsrWireModel Ssr { get; set; } = new(); + + public static BrowserConfigurationWireModel FromOptions(BrowserOptions options) + { + var wire = new BrowserConfigurationWireModel + { + LogLevel = options.LogLevel is { } logLevel ? (int)logLevel : null, + WebAssembly = new WebAssemblyWireModel + { + EnvironmentName = options.WebAssembly.EnvironmentName, + ApplicationCulture = options.WebAssembly.ApplicationCulture, + EnvironmentVariables = options.WebAssembly.EnvironmentVariables.Count > 0 + ? new Dictionary(options.WebAssembly.EnvironmentVariables) + : null, + }, + Server = new ServerWireModel + { + ReconnectionMaxRetries = options.Server.ReconnectionMaxRetries, + ReconnectionRetryIntervalMilliseconds = options.Server.ReconnectionRetryInterval is { } interval + ? (int)interval.TotalMilliseconds + : null, + ReconnectionDialogId = options.Server.ReconnectionDialogId, + }, + Ssr = new SsrWireModel + { + DisableDomPreservation = options.Ssr.PreserveDom is { } preserveDom ? !preserveDom : null, + CircuitInactivityTimeoutMs = options.Ssr.CircuitInactivityTimeout is { } timeout + ? (int)timeout.TotalMilliseconds + : null, + }, + }; + + return wire; + } + + internal sealed class WebAssemblyWireModel + { + public string? EnvironmentName { get; set; } + + public string? ApplicationCulture { get; set; } + + public IDictionary? EnvironmentVariables { get; set; } + } + + internal sealed class ServerWireModel + { + public int? ReconnectionMaxRetries { get; set; } + + public int? ReconnectionRetryIntervalMilliseconds { get; set; } + + public string? ReconnectionDialogId { get; set; } + } + + internal sealed class SsrWireModel + { + public bool? DisableDomPreservation { get; set; } + + public int? CircuitInactivityTimeoutMs { get; set; } + } +} diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs index 2890acec7005..ddf01f537795 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs @@ -344,7 +344,7 @@ private void EmitBrowserConfigurationOnce(TextWriter output) _browserConfigurationEmitted = true; - var config = _httpContext.GetBrowserConfiguration(); + var config = _httpContext.GetBrowserOptions(); // Apply framework defaults: environment name and tooling env vars var hostEnvironment = _httpContext.RequestServices.GetRequiredService(); @@ -360,7 +360,8 @@ private void EmitBrowserConfigurationOnce(TextWriter output) config.WebAssembly.EnvironmentVariables.TryAdd("__ASPNETCORE_BROWSER_TOOLS", s_aspnetcoreBrowserTools); } - var configJson = JsonSerializer.Serialize(config, BrowserConfigurationJsonContext.Default.BrowserConfiguration); + var wireModel = BrowserConfigurationWireModel.FromOptions(config); + var configJson = JsonSerializer.Serialize(wireModel, BrowserConfigurationJsonContext.Default.BrowserConfigurationWireModel); var configBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(configJson)); output.Write("