diff --git a/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs
index 02addb8e468b..f34254be0816 100644
--- a/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs
@@ -5,9 +5,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
+using Microsoft.CodeAnalysis.CSharp.Symbols;
namespace Microsoft.AspNetCore.Builder
{
@@ -184,6 +186,23 @@ public static MinimalActionEndpointConventionBuilder Map(
// Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint.
builder.Metadata.Add(action.Method);
+ // Methods defined in a top-level program are generated as statics so the delegate
+ // target will be null. Inline lambdas are compiler generated properties so they can
+ // be filtered that way.
+ if (action.Target == null || !TypeHelper.IsCompilerGenerated(action.Method.Name))
+ {
+ if (GeneratedNameParser.TryParseLocalFunctionName(action.Method.Name, out var endpointName))
+ {
+ builder.Metadata.Add(new EndpointNameMetadata(endpointName));
+ builder.Metadata.Add(new RouteNameMetadata(endpointName));
+ }
+ else
+ {
+ builder.Metadata.Add(new EndpointNameMetadata(action.Method.Name));
+ builder.Metadata.Add(new RouteNameMetadata(action.Method.Name));
+ }
+ }
+
// Add delegate attributes as metadata
var attributes = action.Method.GetCustomAttributes();
diff --git a/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs b/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
index 24b9b3df6c88..4c29a1dce365 100644
--- a/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Linq;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Builder
diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
index 4c0c0a1a5443..dabaeffa8273 100644
--- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
+++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
@@ -24,6 +24,10 @@ Microsoft.AspNetCore.Routing.RouteCollection
+
+
+
+
diff --git a/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs
index 4dd5bbaa4569..6bb049c116aa 100644
--- a/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs
@@ -359,6 +359,71 @@ public void MapFallbackWithoutPath_BuildsEndpointWithLowestRouteOrder()
Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
}
+ [Fact]
+ // This test scenario simulates methods defined in a top-level program
+ // which are compiler generated. We currently do some manually parsing leveraging
+ // code in Roslyn to support this scenario. More info at https://github.com/dotnet/roslyn/issues/55651.
+ public void MapMethod_DoesNotEndpointNameForInnerMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ string InnerGetString() => "TestString";
+ _ = builder.MapDelete("/", InnerGetString);
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata();
+ Assert.NotNull(endpointName);
+ Assert.Equal("InnerGetString", endpointName?.EndpointName);
+ }
+
+ [Fact]
+ public void MapMethod_SetsEndpointNameForMethodGroup()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ _ = builder.MapDelete("/", GetString);
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata();
+ Assert.NotNull(endpointName);
+ Assert.Equal("GetString", endpointName?.EndpointName);
+ }
+
+ [Fact]
+ public void WithNameOverridesDefaultEndpointName()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ _ = builder.MapDelete("/", GetString).WithName("SomeCustomName");
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata();
+ Assert.NotNull(endpointName);
+ Assert.Equal("SomeCustomName", endpointName?.EndpointName);
+ }
+
+ private string GetString() => "TestString";
+
+ [Fact]
+ public void MapMethod_DoesNotSetEndpointNameForLambda()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ _ = builder.MapDelete("/", () => { });
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata();
+ Assert.Null(endpointName);
+ }
+
class FromRoute : Attribute, IFromRouteMetadata
{
public string? Name { get; set; }
diff --git a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs
index fc3f69701a1e..d6a93763e6e4 100644
--- a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs
+++ b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs
@@ -78,7 +78,7 @@ private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string
// For now, put all methods defined the same declaring type together.
string controllerName;
- if (methodInfo.DeclaringType is not null && !IsCompilerGenerated(methodInfo.DeclaringType))
+ if (methodInfo.DeclaringType is not null && !TypeHelper.IsCompilerGenerated(methodInfo.DeclaringType.Name, methodInfo.DeclaringType))
{
controllerName = methodInfo.DeclaringType.Name;
}
@@ -363,11 +363,5 @@ private static void AddActionDescriptorEndpointMetadata(
actionDescriptor.EndpointMetadata = new List