Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Routing.Patterns;

namespace Microsoft.AspNetCore.Mvc.ApiExplorer;

Expand Down Expand Up @@ -42,6 +43,11 @@ public class ApiDescription
/// </summary>
public string? RelativePath { get; set; }

/// <summary>
/// Gets or sets the route pattern for this api.
/// </summary>
public RoutePattern? RoutePattern { get; set; }

/// <summary>
/// Gets the list of possible formats for a request.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Microsoft.AspNetCore.Mvc.IActionResult</Description>
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
<Reference Include="Microsoft.Net.Http.Headers" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -97,13 +98,17 @@ 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()
{
ActionDescriptor = action,
GroupName = groupName,
HttpMethod = httpMethod,
RelativePath = GetRelativePath(parsedTemplate),
RoutePattern = routePattern,
};

var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List<TemplatePart>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ private ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, string
HttpMethod = httpMethod,
GroupName = routeEndpoint.Metadata.GetMetadata<IEndpointGroupNameMetadata>()?.EndpointGroupName,
RelativePath = routeEndpoint.RoutePattern.RawText?.TrimStart('/'),
RoutePattern = routeEndpoint.RoutePattern,
ActionDescriptor = new ActionDescriptor
{
DisplayName = routeEndpoint.DisplayName,
Expand Down
2 changes: 2 additions & 0 deletions src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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('/');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Net.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Routing.Patterns;

public class ApiDescriptionExtensionsTests
{
Expand Down Expand Up @@ -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<object[]> TestCases => new List<object[]>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,41 @@ await VerifyOpenApiDocument(builder, document =>
);
});
}

[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

{
// 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);
}
);
});
}
}
Loading