From b17b4a5edddc5d4d9d96630dfb727b73cfeda277 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 25 Sep 2025 11:47:10 +0000
Subject: [PATCH 1/3] Initial plan
From 12c12a1ad5fae04f99c6493eaf962f3b97c2ffa0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 25 Sep 2025 11:59:46 +0000
Subject: [PATCH 2/3] Add ComponentFrameworkEndpointMetadata to identify Blazor
infrastructure endpoints
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../ComponentFrameworkEndpointMetadata.cs | 13 +++++
.../src/Builder/OpaqueRedirection.cs | 1 +
.../Endpoints/src/PublicAPI.Unshipped.txt | 2 +
.../ComponentFrameworkEndpointMetadataTest.cs | 41 ++++++++++++++++
.../test/Builder/OpaqueRedirectionTest.cs | 48 +++++++++++++++++++
...ComponentEndpointRouteBuilderExtensions.cs | 7 ++-
...onentEndpointRouteBuilderExtensionsTest.cs | 32 +++++++++++++
.../Server/src/WebAssemblyEndpointProvider.cs | 20 +++++++-
8 files changed, 161 insertions(+), 3 deletions(-)
create mode 100644 src/Components/Endpoints/src/Builder/ComponentFrameworkEndpointMetadata.cs
create mode 100644 src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
create mode 100644 src/Components/Endpoints/test/Builder/OpaqueRedirectionTest.cs
diff --git a/src/Components/Endpoints/src/Builder/ComponentFrameworkEndpointMetadata.cs b/src/Components/Endpoints/src/Builder/ComponentFrameworkEndpointMetadata.cs
new file mode 100644
index 000000000000..2bd084c0e564
--- /dev/null
+++ b/src/Components/Endpoints/src/Builder/ComponentFrameworkEndpointMetadata.cs
@@ -0,0 +1,13 @@
+// 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;
+
+///
+/// Metadata that identifies infrastructure endpoints for Blazor framework functionality.
+/// This marker is used to distinguish framework endpoints (like opaque redirection,
+/// disconnect, and JavaScript initializers) from regular component endpoints.
+///
+public sealed class ComponentFrameworkEndpointMetadata
+{
+}
\ No newline at end of file
diff --git a/src/Components/Endpoints/src/Builder/OpaqueRedirection.cs b/src/Components/Endpoints/src/Builder/OpaqueRedirection.cs
index 7e747aca96ac..97e39f605f28 100644
--- a/src/Components/Endpoints/src/Builder/OpaqueRedirection.cs
+++ b/src/Components/Endpoints/src/Builder/OpaqueRedirection.cs
@@ -59,6 +59,7 @@ public static EndpointBuilder GetBlazorOpaqueRedirectionEndpoint()
};
routeEndpointBuidler.Metadata.Add(new HttpMethodMetadata([HttpMethods.Get]));
+ routeEndpointBuidler.Metadata.Add(new ComponentFrameworkEndpointMetadata());
return routeEndpointBuidler;
}
diff --git a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
index 424ca155339b..931da44ec99c 100644
--- a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
+Microsoft.AspNetCore.Components.Endpoints.ComponentFrameworkEndpointMetadata
+Microsoft.AspNetCore.Components.Endpoints.ComponentFrameworkEndpointMetadata.ComponentFrameworkEndpointMetadata() -> void
Microsoft.AspNetCore.Components.ResourcePreloader
Microsoft.AspNetCore.Components.ResourcePreloader.ResourcePreloader() -> void
Microsoft.Extensions.DependencyInjection.RazorComponentsRazorComponentBuilderExtensions
diff --git a/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs b/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
new file mode 100644
index 000000000000..50542b31cfe4
--- /dev/null
+++ b/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
@@ -0,0 +1,41 @@
+// 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.Endpoints;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components.Tests.Builder;
+
+public class ComponentFrameworkEndpointMetadataTest
+{
+ [Fact]
+ public void ComponentFrameworkEndpointMetadata_CanBeCreated()
+ {
+ // Arrange & Act
+ var metadata = new ComponentFrameworkEndpointMetadata();
+
+ // Assert
+ Assert.NotNull(metadata);
+ }
+
+ [Fact]
+ public void ComponentFrameworkEndpointMetadata_IsSealed()
+ {
+ // Arrange & Act
+ var type = typeof(ComponentFrameworkEndpointMetadata);
+
+ // Assert
+ Assert.True(type.IsSealed);
+ }
+
+ [Fact]
+ public void ComponentFrameworkEndpointMetadata_HasNoPublicProperties()
+ {
+ // Arrange & Act
+ var type = typeof(ComponentFrameworkEndpointMetadata);
+ var properties = type.GetProperties();
+
+ // Assert
+ Assert.Empty(properties);
+ }
+}
\ No newline at end of file
diff --git a/src/Components/Endpoints/test/Builder/OpaqueRedirectionTest.cs b/src/Components/Endpoints/test/Builder/OpaqueRedirectionTest.cs
new file mode 100644
index 000000000000..538fcf7f42b5
--- /dev/null
+++ b/src/Components/Endpoints/test/Builder/OpaqueRedirectionTest.cs
@@ -0,0 +1,48 @@
+// 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.Endpoints;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components.Tests.Builder;
+
+public class OpaqueRedirectionTest
+{
+ [Fact]
+ public void GetBlazorOpaqueRedirectionEndpoint_ContainsComponentFrameworkEndpointMetadata()
+ {
+ // Arrange & Act
+ var endpointBuilder = OpaqueRedirection.GetBlazorOpaqueRedirectionEndpoint();
+ var endpoint = endpointBuilder.Build();
+
+ // Assert
+ var metadata = endpoint.Metadata.GetMetadata();
+ Assert.NotNull(metadata);
+ }
+
+ [Fact]
+ public void GetBlazorOpaqueRedirectionEndpoint_HasCorrectDisplayName()
+ {
+ // Arrange & Act
+ var endpointBuilder = OpaqueRedirection.GetBlazorOpaqueRedirectionEndpoint();
+ var endpoint = endpointBuilder.Build();
+
+ // Assert
+ Assert.Equal("Blazor Opaque Redirection", endpoint.DisplayName);
+ }
+
+ [Fact]
+ public void GetBlazorOpaqueRedirectionEndpoint_HasHttpGetMethod()
+ {
+ // Arrange & Act
+ var endpointBuilder = OpaqueRedirection.GetBlazorOpaqueRedirectionEndpoint();
+ var endpoint = endpointBuilder.Build();
+
+ // Assert
+ var httpMethodMetadata = endpoint.Metadata.GetMetadata();
+ Assert.NotNull(httpMethodMetadata);
+ Assert.Contains(HttpMethods.Get, httpMethodMetadata.HttpMethods);
+ }
+}
\ No newline at end of file
diff --git a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
index 26cca2322d0f..c5950981ea29 100644
--- a/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
+++ b/src/Components/Server/src/Builder/ComponentEndpointRouteBuilderExtensions.cs
@@ -1,6 +1,7 @@
// 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.Endpoints;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
@@ -78,12 +79,14 @@ public static ComponentEndpointConventionBuilder MapBlazorHub(
var disconnectEndpoint = endpoints.Map(
(path.EndsWith('/') ? path : path + "/") + "disconnect/",
endpoints.CreateApplicationBuilder().UseMiddleware().Build())
- .WithDisplayName("Blazor disconnect");
+ .WithDisplayName("Blazor disconnect")
+ .WithMetadata(new ComponentFrameworkEndpointMetadata());
var jsInitializersEndpoint = endpoints.Map(
(path.EndsWith('/') ? path : path + "/") + "initializers/",
endpoints.CreateApplicationBuilder().UseMiddleware().Build())
- .WithDisplayName("Blazor initializers");
+ .WithDisplayName("Blazor initializers")
+ .WithMetadata(new ComponentFrameworkEndpointMetadata());
return new ComponentEndpointConventionBuilder(hubEndpoint, disconnectEndpoint, jsInitializersEndpoint);
}
diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
index 5d28dae0be1a..30c75962172a 100644
--- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
@@ -122,6 +123,37 @@ public void MapBlazorHub_AppliesFinalConventionsInFIFOOrder()
Assert.Equal(new[] { "first-in", "last-in" }, populatedMetadata);
}
+ [Fact]
+ public void MapBlazorHub_AddsComponentFrameworkEndpointMetadata()
+ {
+ // Arrange
+ var applicationBuilder = CreateAppBuilder();
+ var frameworkEndpoints = new List();
+
+ // Act
+ var app = applicationBuilder
+ .UseRouting()
+ .UseEndpoints(endpoints =>
+ {
+ endpoints
+ .MapBlazorHub()
+ .Finally(builder =>
+ {
+ if (builder.Metadata.GetMetadata() is not null)
+ {
+ frameworkEndpoints.Add(builder.DisplayName);
+ }
+ });
+ }).Build();
+
+ // Trigger endpoint construction
+ app.Invoke(new DefaultHttpContext());
+
+ // Assert
+ Assert.Contains("Blazor disconnect", frameworkEndpoints);
+ Assert.Contains("Blazor initializers", frameworkEndpoints);
+ }
+
private IApplicationBuilder CreateAppBuilder()
{
var environment = new Mock();
diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyEndpointProvider.cs b/src/Components/WebAssembly/Server/src/WebAssemblyEndpointProvider.cs
index 7176462b7998..2f5186014a73 100644
--- a/src/Components/WebAssembly/Server/src/WebAssemblyEndpointProvider.cs
+++ b/src/Components/WebAssembly/Server/src/WebAssemblyEndpointProvider.cs
@@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Endpoints;
using Microsoft.AspNetCore.Components.Endpoints.Infrastructure;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Http;
@@ -41,7 +42,8 @@ public override IEnumerable GetEndpointBuilders(IComponent
context.SetEndpoint(null);
return app(context);
- });
+ })
+ .WithMetadata(new ComponentFrameworkEndpointMetadata());
return endpointRouteBuilder.GetEndpoints();
}
@@ -75,6 +77,22 @@ internal IEnumerable GetEndpoints()
builder.Metadata.Add(metadata);
}
+ // Add framework endpoint metadata if not already present
+ var hasFrameworkMetadata = false;
+ for (var i = 0; i < builder.Metadata.Count; i++)
+ {
+ if (builder.Metadata[i] is ComponentFrameworkEndpointMetadata)
+ {
+ hasFrameworkMetadata = true;
+ break;
+ }
+ }
+
+ if (!hasFrameworkMetadata)
+ {
+ builder.Metadata.Add(new ComponentFrameworkEndpointMetadata());
+ }
+
yield return builder;
}
}
From 1c1c0b9e9940b36b9271a4faddda1d67197e3e29 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 15:36:28 +0000
Subject: [PATCH 3/3] Address feedback: Remove test, fix compilation error, add
ConfigureFrameworkEndpoints method
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
...entsEndpointConventionBuilderExtensions.cs | 32 +++++++++++
.../Endpoints/src/PublicAPI.Unshipped.txt | 1 +
.../ComponentFrameworkEndpointMetadataTest.cs | 41 --------------
...EndpointConventionBuilderExtensionsTest.cs | 53 +++++++++++++++++++
...onentEndpointRouteBuilderExtensionsTest.cs | 3 +-
5 files changed, 88 insertions(+), 42 deletions(-)
delete mode 100644 src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
diff --git a/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs b/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs
index fd0a6aec0c4d..73941a78a32d 100644
--- a/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs
+++ b/src/Components/Endpoints/src/Builder/RazorComponentsEndpointConventionBuilderExtensions.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Endpoints;
@@ -63,4 +64,35 @@ public static RazorComponentsEndpointConventionBuilder WithStaticAssets(
return builder;
}
+
+ ///
+ /// Configures framework endpoints for the Blazor application. Framework endpoints are internal
+ /// infrastructure endpoints such as opaque redirection, disconnect, and initializers.
+ ///
+ /// The .
+ /// A callback to configure framework endpoints.
+ /// The .
+ ///
+ /// This method provides a way to apply specific conventions or metadata to Blazor's infrastructure
+ /// endpoints without affecting regular component endpoints. Framework endpoints are identified by
+ /// the presence of in their metadata collection.
+ ///
+ public static RazorComponentsEndpointConventionBuilder ConfigureFrameworkEndpoints(
+ this RazorComponentsEndpointConventionBuilder builder,
+ Action configureEndpoints)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configureEndpoints);
+
+ builder.Add(endpointBuilder =>
+ {
+ // Only apply configuration to endpoints that have ComponentFrameworkEndpointMetadata
+ if (endpointBuilder.Metadata.OfType().Any())
+ {
+ configureEndpoints(endpointBuilder);
+ }
+ });
+
+ return builder;
+ }
}
diff --git a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
index 931da44ec99c..18a9e656a63f 100644
--- a/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Endpoints/src/PublicAPI.Unshipped.txt
@@ -4,4 +4,5 @@ Microsoft.AspNetCore.Components.Endpoints.ComponentFrameworkEndpointMetadata.Com
Microsoft.AspNetCore.Components.ResourcePreloader
Microsoft.AspNetCore.Components.ResourcePreloader.ResourcePreloader() -> void
Microsoft.Extensions.DependencyInjection.RazorComponentsRazorComponentBuilderExtensions
+static Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilderExtensions.ConfigureFrameworkEndpoints(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action! configureEndpoints) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder!
static Microsoft.Extensions.DependencyInjection.RazorComponentsRazorComponentBuilderExtensions.RegisterPersistentService(this Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! builder, Microsoft.AspNetCore.Components.IComponentRenderMode! renderMode) -> Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder!
diff --git a/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs b/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
deleted file mode 100644
index 50542b31cfe4..000000000000
--- a/src/Components/Endpoints/test/Builder/ComponentFrameworkEndpointMetadataTest.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.Endpoints;
-using Xunit;
-
-namespace Microsoft.AspNetCore.Components.Tests.Builder;
-
-public class ComponentFrameworkEndpointMetadataTest
-{
- [Fact]
- public void ComponentFrameworkEndpointMetadata_CanBeCreated()
- {
- // Arrange & Act
- var metadata = new ComponentFrameworkEndpointMetadata();
-
- // Assert
- Assert.NotNull(metadata);
- }
-
- [Fact]
- public void ComponentFrameworkEndpointMetadata_IsSealed()
- {
- // Arrange & Act
- var type = typeof(ComponentFrameworkEndpointMetadata);
-
- // Assert
- Assert.True(type.IsSealed);
- }
-
- [Fact]
- public void ComponentFrameworkEndpointMetadata_HasNoPublicProperties()
- {
- // Arrange & Act
- var type = typeof(ComponentFrameworkEndpointMetadata);
- var properties = type.GetProperties();
-
- // Assert
- Assert.Empty(properties);
- }
-}
\ No newline at end of file
diff --git a/src/Components/Endpoints/test/Builder/RazorComponentsEndpointConventionBuilderExtensionsTest.cs b/src/Components/Endpoints/test/Builder/RazorComponentsEndpointConventionBuilderExtensionsTest.cs
index 70fa780fcbaf..6c7afea60c7d 100644
--- a/src/Components/Endpoints/test/Builder/RazorComponentsEndpointConventionBuilderExtensionsTest.cs
+++ b/src/Components/Endpoints/test/Builder/RazorComponentsEndpointConventionBuilderExtensionsTest.cs
@@ -312,6 +312,59 @@ public override void Write(string name, object value) { }
}
}
+ [Fact]
+ public void ConfigureFrameworkEndpoints_AppliesConfigurationOnlyToFrameworkEndpoints()
+ {
+ // Arrange
+ var endpointBuilder = new TestEndpointRouteBuilder();
+ var builder = CreateRazorComponentsAppBuilder(endpointBuilder);
+ var configuredEndpoints = new List();
+
+ // Act
+ builder.ConfigureFrameworkEndpoints(endpointBuilder =>
+ {
+ configuredEndpoints.Add(endpointBuilder.DisplayName ?? "Unknown");
+ });
+
+ // Create endpoints to trigger configuration
+ var endpoints = endpointBuilder.DataSources.First().Endpoints.ToList();
+
+ // Assert - no endpoints should be configured since we haven't added framework metadata
+ Assert.Empty(configuredEndpoints);
+ }
+
+ [Fact]
+ public void ConfigureFrameworkEndpoints_OnlyConfiguresEndpointsWithFrameworkMetadata()
+ {
+ // Arrange
+ var endpointBuilder = new TestEndpointRouteBuilder();
+ var builder = CreateRazorComponentsAppBuilder(endpointBuilder);
+ var configuredEndpoints = new List();
+
+ builder.Add(endpoint =>
+ {
+ // Simulate framework endpoint by adding the metadata
+ if (endpoint.DisplayName == "TestFrameworkEndpoint")
+ {
+ endpoint.Metadata.Add(new ComponentFrameworkEndpointMetadata());
+ }
+ endpoint.DisplayName ??= "TestFrameworkEndpoint";
+ });
+
+ // Act
+ builder.ConfigureFrameworkEndpoints(endpointBuilder =>
+ {
+ configuredEndpoints.Add(endpointBuilder.DisplayName ?? "Unknown");
+ });
+
+ // Create endpoints to trigger configuration
+ var endpoints = endpointBuilder.DataSources.First().Endpoints.ToList();
+
+ // Assert
+ Assert.Single(configuredEndpoints);
+ Assert.Contains("TestFrameworkEndpoint", configuredEndpoints);
+ }
+
private class App : IComponent
{
void IComponent.Attach(RenderHandle renderHandle) => throw new NotImplementedException();
diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
index 30c75962172a..b965d794b532 100644
--- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs
@@ -139,7 +139,8 @@ public void MapBlazorHub_AddsComponentFrameworkEndpointMetadata()
.MapBlazorHub()
.Finally(builder =>
{
- if (builder.Metadata.GetMetadata() is not null)
+ // Check if metadata collection contains ComponentFrameworkEndpointMetadata
+ if (builder.Metadata.OfType().Any())
{
frameworkEndpoints.Add(builder.DisplayName);
}