Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 17, 2025

Fix OpenAPI generator crash on routes starting with '~'

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Store RoutePattern on ApiDescription to avoid reparsing

Description

MapRelativePathToItemPath was reparsing RelativePath with RoutePatternFactory.Parse(), which fails for routes starting with ~ since the stored path lacks the leading / (e.g., ~health vs /~health).

Solution: Add RoutePattern property to ApiDescription and populate it in both providers:

  • EndpointMetadataApiDescriptionProvider uses routeEndpoint.RoutePattern
  • DefaultApiDescriptionProvider parses from AttributeRouteInfo.Template
  • MapRelativePathToItemPath uses stored pattern, falls back to parsing for compatibility

Example:

builder.MapGet("/~health", () => Results.Ok("Healthy"));
builder.MapOpenApi(); // Previously crashed, now works

Changes:

  • Added RoutePattern property to ApiDescription
  • Updated both API description providers to set the property
  • Modified MapRelativePathToItemPath to use pre-parsed pattern
  • Added routing assembly reference to Mvc.Abstractions
  • Updated PublicAPI files

Tests:

  • Added 3 unit tests verifying routes starting with ~
  • Added integration test for minimal API endpoints with ~ routes
  • Added integration test for MVC controller actions with ~ routes

Fixes #60967

Original prompt

This section details on the original issue you should resolve

<issue_title>Microsoft.AspNetCore.OpenApi: The route template cannot start with a '~' character</issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

The OpenApi generator crashes on routes that starts with a '~' character.

e.g.

app.MapGet("/~health", () => Results.Ok("Works"));

While the endpoint "/~health" works as expected, the openapi endpoint crashes

Expected Behavior

The OpenApi impl should also be compatible with routes that start with ~ (like aspnetcore does).

The ~ may not be pretty from a design approach but the behavior of the OpenApi generator should match the behavior of aspnetcore.

Offtopic:
We are currently considering to migrate from /~endpoint to /.endpoint because we ship internally some default endpoints on every app

Steps To Reproduce

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/~health", () => Results.Ok("Works"));

app.MapOpenApi();

app.Run();

Exceptions (if any)

Microsoft.AspNetCore.Routing.Patterns.RoutePatternException: The route template cannot start with a '~' character unless followed by a '/'.
   at Microsoft.AspNetCore.Routing.Patterns.RoutePatternParser.TrimPrefix(String routePattern)
   at Microsoft.AspNetCore.Routing.Patterns.RoutePatternParser.Parse(String pattern)
   at ApiDescriptionExtensions.MapRelativePathToItemPath(ApiDescription apiDescription)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.<>c.<GetOpenApiPathsAsync>b__20_1(ApiDescription apiDescription)
   at System.Linq.Lookup`2.Create(IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
   at System.Linq.Enumerable.GroupByIterator`2.MoveNext()
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, HttpRequest httpRequest, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F0399B8B15BE443123F4BECEE84B112C1912EE3F44D9DDD7BFA38E371FB917343__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<<MapGet0>g__RequestHandler|5>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

10.0.100

Anything else?

This issue only appears if the route starts with ~

e.g.

app.MapGet("/x~health", () => Results.Ok("Works"));

would work</issue_description>

<agent_instructions>Add a RoutePattern field to the ApiDescription type.

Set the field to the correct value in EndpointMetadataApiDescriptionProvider and DefaultApiDescriptionProvider.

Update ApiDescriptionExtensions.MapRelativePathToItemPath to use this new value.

Add tests to Microsoft.AspNetCore.OpenApi.Tests to validate the behavior when the /~health route is used on an endpoint.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@captainsafia @dp-sgr Great bug report! The fact that we pass the relative path to the RoutePatternParser is giving us grieve here.

The best way to address this is probably for us to store the RoutePattern somewhere on the ApiDescription so that we don't have to recreate it here. That'll probably require some tweaks to the shared framework but might be goodness all up.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Nov 17, 2025
@dotnet-policy-service
Copy link
Contributor

Greetings human! You've submitted a PR that modifies code that is shared with https://github.com/dotnet/runtime . Please make sure you synchronize this code with the changes in that repo!

}

[Fact]
public async Task GetOpenApiPaths_HandlesRoutesStartingWithTilde()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Add a test that validates the behavior of these types of paths from a controller action.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test GetOpenApiPaths_HandlesRoutesStartingWithTilde_MvcAction that validates routes starting with ~ from MVC controller actions. The test uses a controller action with [Route("/~health")] attribute and verifies the OpenAPI document is generated correctly. Commit: e48e103

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Microsoft.AspNetCore.OpenApi: The route template cannot start with a '~' character

2 participants