From 65cca984d14d4101616a8b472c0badd63a5c78ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:14:34 +0000 Subject: [PATCH 1/4] Initial plan From 373927fd4b188e5831fcc53df739d9600336aade Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:31:31 +0000 Subject: [PATCH 2/4] Add RoutePattern field to ApiDescription and update OpenApi extension to use it Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../src/ApiExplorer/ApiDescription.cs | 6 +++++ ...crosoft.AspNetCore.Mvc.Abstractions.csproj | 1 + .../src/PublicAPI.Unshipped.txt | 2 ++ .../src/DefaultApiDescriptionProvider.cs | 5 +++++ .../EndpointMetadataApiDescriptionProvider.cs | 1 + .../src/PublicAPI.Unshipped.txt | 2 ++ .../Extensions/ApiDescriptionExtensions.cs | 2 +- .../ApiDescriptionExtensionsTests.cs | 22 +++++++++++++++++++ 8 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs index 0f6fe9f6557d..df32c776dfa9 100644 --- a/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs +++ b/src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -42,6 +43,11 @@ public class ApiDescription /// public string? RelativePath { get; set; } + /// + /// Gets or sets the route pattern for this api. + /// + public RoutePattern? RoutePattern { get; set; } + /// /// Gets the list of possible formats for a request. /// diff --git a/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj index 16a5411f1caf..0b0c56d86aeb 100644 --- a/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Mvc/Mvc.Abstractions/src/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -28,6 +28,7 @@ Microsoft.AspNetCore.Mvc.IActionResult + diff --git a/src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..012d03ae8f56 100644 --- a/src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription.RoutePattern.get -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern? +Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription.RoutePattern.set -> void diff --git a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs index 75f0f194193b..99b8218b35da 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Options; @@ -97,6 +98,9 @@ private ApiDescription CreateApiDescription( string? groupName) { var parsedTemplate = ParseTemplate(action); + var routePattern = parsedTemplate != null && action.AttributeRouteInfo?.Template != null + ? RoutePatternFactory.Parse(action.AttributeRouteInfo.Template) + : null; var apiDescription = new ApiDescription() { @@ -104,6 +108,7 @@ private ApiDescription CreateApiDescription( GroupName = groupName, HttpMethod = httpMethod, RelativePath = GetRelativePath(parsedTemplate), + RoutePattern = routePattern, }; var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List(); diff --git a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs index cdd33a92fe22..0b40e8813269 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/EndpointMetadataApiDescriptionProvider.cs @@ -102,6 +102,7 @@ private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string HttpMethod = httpMethod, GroupName = routeEndpoint.Metadata.GetMetadata()?.EndpointGroupName, RelativePath = routeEndpoint.RoutePattern.RawText?.TrimStart('/'), + RoutePattern = routeEndpoint.RoutePattern, ActionDescriptor = new ActionDescriptor { DisplayName = routeEndpoint.DisplayName, diff --git a/src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..3cec56717bbb 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription.RoutePattern.get -> Microsoft.AspNetCore.Routing.Patterns.RoutePattern? (forwarded, contained in Microsoft.AspNetCore.Mvc.Abstractions) +Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription.RoutePattern.set -> void (forwarded, contained in Microsoft.AspNetCore.Mvc.Abstractions) diff --git a/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs b/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs index a6e5b86944a1..3fdf20c32fbe 100644 --- a/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs +++ b/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs @@ -49,7 +49,7 @@ public static string MapRelativePathToItemPath(this ApiDescription apiDescriptio return "/"; } var strippedRoute = new StringBuilder(); - var routePattern = RoutePatternFactory.Parse(apiDescription.RelativePath); + var routePattern = apiDescription.RoutePattern ?? RoutePatternFactory.Parse(apiDescription.RelativePath); for (var i = 0; i < routePattern.PathSegments.Count; i++) { strippedRoute.Append('/'); diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs index 0fbbce9ac27a..dab07110ffff 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs @@ -3,6 +3,7 @@ using System.Net.Http; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Routing.Patterns; public class ApiDescriptionExtensionsTests { @@ -31,6 +32,27 @@ public void MapRelativePathToItemPath_ReturnsItemPathForApiDescription(string re Assert.Equal(expectedItemPath, itemPath); } + [Theory] + [InlineData("/~health", "~health", "/~health")] + [InlineData("/~api/todos", "~api/todos", "/~api/todos")] + [InlineData("/~api/todos/{id}", "~api/todos/{id}", "/~api/todos/{id}")] + public void MapRelativePathToItemPath_WithRoutePattern_HandlesRoutesThatStartWithTilde(string rawPattern, string relativePath, string expectedItemPath) + { + // Arrange + var routePattern = RoutePatternFactory.Parse(rawPattern); + var apiDescription = new ApiDescription + { + RelativePath = relativePath, + RoutePattern = routePattern + }; + + // Act + var itemPath = apiDescription.MapRelativePathToItemPath(); + + // Assert + Assert.Equal(expectedItemPath, itemPath); + } + public static class HttpMethodTestData { public static IEnumerable TestCases => new List From 4f63d6482c26a3716108d5853776ac0fd831d524 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:34:55 +0000 Subject: [PATCH 3/4] Add integration test for routes starting with tilde Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../OpenApiDocumentServiceTests.Paths.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs index adde85a2e116..1c00df360c99 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs @@ -170,4 +170,41 @@ await VerifyOpenApiDocument(builder, document => ); }); } + + [Fact] + public async Task GetOpenApiPaths_HandlesRoutesStartingWithTilde() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapGet("/~health", () => "Healthy"); + builder.MapGet("/~api/todos", () => { }); + builder.MapGet("/~api/todos/{id}", () => { }); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + Assert.Collection(document.Paths.OrderBy(p => p.Key), + path => + { + Assert.Equal("/~api/todos", path.Key); + Assert.Single(path.Value.Operations); + Assert.Contains(HttpMethod.Get, path.Value.Operations); + }, + path => + { + Assert.Equal("/~api/todos/{id}", path.Key); + Assert.Single(path.Value.Operations); + Assert.Contains(HttpMethod.Get, path.Value.Operations); + }, + path => + { + Assert.Equal("/~health", path.Key); + Assert.Single(path.Value.Operations); + Assert.Contains(HttpMethod.Get, path.Value.Operations); + } + ); + }); + } } From e48e103b95cc7a9411fa8de0f909651739cffc00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 01:19:53 +0000 Subject: [PATCH 4/4] Add test for routes starting with tilde from MVC controller actions Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../OpenApiDocumentServiceTests.Paths.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs index 1c00df360c99..b074b38a7d63 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.Paths.cs @@ -3,6 +3,7 @@ using System.Net.Http; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Routing; @@ -207,4 +208,27 @@ await VerifyOpenApiDocument(builder, document => ); }); } + + [Fact] + public async Task GetOpenApiPaths_HandlesRoutesStartingWithTilde_MvcAction() + { + // Arrange + var action = CreateActionDescriptor(nameof(ActionWithTildeRoute)); + + // Assert + await VerifyOpenApiDocument(action, document => + { + Assert.Collection(document.Paths.OrderBy(p => p.Key), + path => + { + Assert.Equal("/~health", path.Key); + Assert.Single(path.Value.Operations); + Assert.Contains(HttpMethod.Get, path.Value.Operations); + } + ); + }); + } + + [Route("/~health")] + private void ActionWithTildeRoute() { } }