diff --git a/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/MinimalActionEndpointRouteBuilderExtensions.cs
index 02addb8e468b..15281a28ccb3 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
{
@@ -107,7 +109,8 @@ public static MinimalActionEndpointConventionBuilder MapMethods(
}
var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), action);
- builder.WithDisplayName($"{pattern} HTTP: {string.Join(", ", httpMethods)}");
+ // Prepends the HTTP method to the DisplayName produced with pattern + method name
+ builder.Add(b => b.DisplayName = $"HTTP: {string.Join(", ", httpMethods)} {b.DisplayName}");
builder.WithMetadata(new HttpMethodMetadata(httpMethods));
return builder;
}
@@ -184,6 +187,19 @@ 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 method so they can
+ // be filtered that way.
+ if (GeneratedNameParser.TryParseLocalFunctionName(action.Method.Name, out var endpointName)
+ || !TypeHelper.IsCompilerGeneratedMethod(action.Method))
+ {
+ endpointName ??= action.Method.Name;
+
+ builder.Metadata.Add(new EndpointNameMetadata(endpointName));
+ builder.Metadata.Add(new RouteNameMetadata(endpointName));
+ builder.DisplayName = $"{builder.DisplayName} => {endpointName}";
+ }
+
// Add delegate attributes as metadata
var attributes = action.Method.GetCustomAttributes();
diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
index 4c0c0a1a5443..e92de9551933 100644
--- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
+++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
@@ -24,6 +24,8 @@ 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..2d1edcbd7094 100644
--- a/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/MinimalActionEndpointRouteBuilderExtensionsTest.cs
@@ -99,7 +99,7 @@ public void MapGet_BuildsEndpointWithCorrectMethod()
Assert.Equal("GET", method);
var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("/ HTTP: GET", routeEndpointBuilder.DisplayName);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
}
@@ -125,7 +125,7 @@ public async Task MapGetWithRouteParameter_BuildsEndpointWithRouteSpecificBindin
Assert.Equal("GET", method);
var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("/{id} HTTP: GET", routeEndpointBuilder.DisplayName);
+ Assert.Equal("HTTP: GET /{id}", routeEndpointBuilder.DisplayName);
Assert.Equal("/{id}", routeEndpointBuilder.RoutePattern.RawText);
// Assert that we don't fallback to the query string
@@ -163,7 +163,7 @@ public async Task MapGetWithoutRouteParameter_BuildsEndpointWithQuerySpecificBin
Assert.Equal("GET", method);
var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("/ HTTP: GET", routeEndpointBuilder.DisplayName);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
// Assert that we don't fallback to the route values
@@ -205,7 +205,7 @@ public async Task MapVerbWithExplicitRouteParameterIsCaseInsensitive(Action "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();
+ var routeName = endpoint.Metadata.GetMetadata();
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal(name, endpointName?.EndpointName);
+ Assert.Equal(name, routeName?.RouteName);
+ Assert.Equal("HTTP: DELETE / => InnerGetString", routeEndpointBuilder.DisplayName);
+ }
+
+ [Fact]
+ public void MapMethod_DoesNotEndpointNameForInnerMethodWithTarget()
+ {
+ var name = "InnerGetString";
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ var testString = "TestString";
+ 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();
+ var routeName = endpoint.Metadata.GetMetadata();
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal(name, endpointName?.EndpointName);
+ Assert.Equal(name, routeName?.RouteName);
+ Assert.Equal("HTTP: DELETE / => InnerGetString", routeEndpointBuilder.DisplayName);
+ }
+
+
+ [Fact]
+ public void MapMethod_SetsEndpointNameForMethodGroup()
+ {
+ var name = "GetString";
+ 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();
+ var routeName = endpoint.Metadata.GetMetadata();
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal(name, endpointName?.EndpointName);
+ Assert.Equal(name, routeName?.RouteName);
+ Assert.Equal("HTTP: DELETE / => GetString", routeEndpointBuilder.DisplayName);
+ }
+
+ [Fact]
+ public void WithNameOverridesDefaultEndpointName()
+ {
+ var name = "SomeCustomName";
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvdier()));
+ _ = builder.MapDelete("/", GetString).WithName(name);
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata();
+ var routeName = endpoint.Metadata.GetMetadata();
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal(name, endpointName?.EndpointName);
+ Assert.Equal(name, routeName?.RouteName);
+ // Will still use the original method name, not the custom endpoint name
+ Assert.Equal("HTTP: DELETE / => GetString", routeEndpointBuilder.DisplayName);
+ }
+
+ 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 06084059080c..9bebda2d0709 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.IsCompilerGeneratedType(methodInfo.DeclaringType))
{
controllerName = methodInfo.DeclaringType.Name;
}
@@ -364,11 +364,5 @@ private static void AddActionDescriptorEndpointMetadata(
actionDescriptor.EndpointMetadata = new List