Skip to content

[OpenAPI] XML comment for parameter is not found when operation parameter name doesn't match the action parameter name #64534

@khellang

Description

@khellang

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

We're a Ruby-heavy shop, which means all our APIs use snake_case naming. In order to keep our C# code idiomatic, we still name our action arguments according to C# standards, so the method has personId, while the API path uses person_id as the name. This breaks the XML documentation support for parameters. Take the following sample:

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net10.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
        <GenerateDocumentationFile>true</GenerateDocumentationFile>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0"/>
        <PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="10.0.0">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
</Project>
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();
builder.Services.AddControllers();

var app = builder.Build();
app.MapOpenApi();
app.MapControllers();
app.Run();

/// <summary>
/// Controller for handling people.
/// </summary>
[ApiController]
public class PersonController : ControllerBase
{
    /// <summary>
    /// Gets a person by its unique ID.
    /// </summary>
    /// <param name="personId">The person's unique ID.</param>
    /// <param name="cancellationToken">A cancellation token to cancel the request.</param>
    /// <returns>The person with the specified <paramref name="personId"/>.</returns>
    /// <response code="200">Returns the requested person.</response>
    /// <response code="404">If the person is not found.</response>
    [HttpGet("{personId:guid}")]
    public async Task<ActionResult> GetPerson(
        [FromRoute] Guid personId,
        CancellationToken cancellationToken)
    {
        await Task.Delay(500, cancellationToken);
        return Ok();
    }
}

Which generates the following OAS:

{
  "openapi": "3.1.1",
  "info": {
    "title": "WebApplication5 | v1",
    "version": "1.0.0"
  },
  "paths": {
    "/{personId}": {
      "get": {
        "tags": [
          "Person"
        ],
        "summary": "Gets a person by its unique ID.",
        "parameters": [
          {
            "name": "personId",
            "in": "path",
            "description": "The person's unique ID.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The person with the specified personId."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Person"
    }
  ]
}

If I change the name of the path argument to person_id:

diff --git a/WebApplication5/Program.cs b/WebApplication5/Program.cs
index dd397db..92ba9a2 100644
--- a/WebApplication5/Program.cs
+++ b/WebApplication5/Program.cs
@@ -22,9 +22,9 @@ public class PersonController : ControllerBase
     /// <param name="personId">The person's unique ID.</param>
     /// <param name="cancellationToken">A cancellation token to cancel the request.</param>
     /// <returns>The person with the specified <paramref name="personId"/>.</returns>
-    [HttpGet("{personId:guid}")]
+    [HttpGet("{person_id:guid}")]
     public async Task<ActionResult> GetPerson(
-        [FromRoute] Guid personId,
+        [FromRoute(Name = "person_id")] Guid personId,
         CancellationToken cancellationToken)
     {
         await Task.Delay(500, cancellationToken);

The parameter description disappears:

diff --git a/WebApplication5/WebApplication5.json b/WebApplication5/WebApplication5.json
index 5d70887..c7b66b5 100644
--- a/WebApplication5/WebApplication5.json
+++ b/WebApplication5/WebApplication5.json
@@ -5,7 +5,7 @@
     "version": "1.0.0"
   },
   "paths": {
-    "/{personId}": {
+    "/{person_id}": {
       "get": {
         "tags": [
           "Person"
@@ -13,9 +13,8 @@
         "summary": "Gets a person by its unique ID.",
         "parameters": [
           {
-            "name": "personId",
+            "name": "person_id",
             "in": "path",
-            "description": "The person's unique ID.",
             "required": true,
             "schema": {
               "type": "string",

This is because the generated XmlCommentOperationTransformer does not respect the Name property from the FromRoute attribute (or any attribute implementing IModelNameProvider).

I can provide a PR with a fix if you want.

Expected Behavior

No matter what the operation parameter is called, it should be able to locate the correct XML documentation.

Steps To Reproduce

See reproduction above.

Exceptions (if any)

No response

.NET Version

10.0.100

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-openapi

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions