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?
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 aRoutePattern field to theApiDescription 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>
Uh oh!
There was an error while loading.Please reload this page.
Fix OpenAPI generator crash on routes starting with '~'
Store RoutePattern on ApiDescription to avoid reparsing
Description
MapRelativePathToItemPathwas reparsingRelativePathwithRoutePatternFactory.Parse(), which fails for routes starting with~since the stored path lacks the leading/(e.g.,~healthvs/~health).Solution: Add
RoutePatternproperty toApiDescriptionand populate it in both providers:EndpointMetadataApiDescriptionProviderusesrouteEndpoint.RoutePatternDefaultApiDescriptionProviderparses fromAttributeRouteInfo.TemplateMapRelativePathToItemPathuses stored pattern, falls back to parsing for compatibilityExample:
Changes:
RoutePatternproperty toApiDescriptionMapRelativePathToItemPathto use pre-parsed patternTests:
~~routes~routesFixes#60967
Original prompt
✨ Let Copilot coding agentset things up for you — coding agent works faster and does higher quality work when set up for your repo.