Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using [AsParameters] annotation in minimal api does not lift summary comments of the model #2658

Closed
andreasuvoss opened this issue May 31, 2023 · 18 comments

Comments

@andreasuvoss
Copy link

Swashbuckle.AspNetCore: 6.5.0
.NET 7

I am not sure if this is a bug or a feature request.

When defining APIs using the minimal API approach, the summaries from models are not lifted from the XML documentation and put into the resulting swagger.json like I would expect. Before migrating to minimal API we used controllers, and the [FromQuery] annotation in a similar way. This gave the expected result. However [FromQuery] is not supported when using minimal API, instead the [AsParameters] annotation was added in .NET 7 .

The summaries are present in the XML file generated when building the project, but not in the swagger.json file.

I have created a minimal example showing that the summaries are not present (in both swagger.json or SwaggerUI) when using the [AsParameter] annotation, but they are included in the schema on the post request using the [FromBody] annotation.

I have a very ugly workaround using the WithOpenApi extension method from the NuGet package Microsoft.AspNetCore.OpenApi to add descriptions to the individual indices of each parameter by modifying the mapping from my minimal example like so:

app.MapGet("/AsParameters", ([AsParameters] AsParametersArgument request) => "Hello World!")
    .WithOpenApi(operation =>
    {
        operation.Parameters[0].Description = "Parameter one description";
        operation.Parameters[1].Description = "Parameter two description";
        return operation;
    });

Minimal example:

Project file:

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <GenerateDocumentationFile>true</GenerateDocumentationFile>
        <NoWarn>1701;1702;1591</NoWarn>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
    </ItemGroup>

</Project>

Program.cs:

using System.Reflection;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    var assemblyName = Assembly.GetExecutingAssembly().FullName?.Split(',')[0];
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{assemblyName}.xml"));
});

var app = builder.Build();

app.MapGet("/AsParameters", ([AsParameters] AsParametersArgument request) => "Hello World!");

app.MapPost("/FromBody", ([FromBody] FromBodyArgument request) => "Hello World!");

app.UseSwagger();
app.UseSwaggerUI();

app.Run();

internal struct AsParametersArgument
{
    /// <summary>
    /// This is a property with the number one - This is nowhere in SwaggerUI
    /// </summary>
    public string PropertyOne { get; set; }

    /// <summary>
    /// This is a property with the number two - This is nowhere in SwaggerUI
    /// </summary>
    public string PropertyTwo { get; set; }
}

internal struct FromBodyArgument
{
    /// <summary>
    /// This is a property with the number eight (this description is present in Swagger)
    /// </summary>
    public string PropertyEight { get; set; }

    /// <summary>
    /// This is a property with the number nine (this description is present in Swagger)
    /// </summary>
    public string PropertyNine { get; set; }
}
@mr-davidc
Copy link

I think I'm running in to a similar issue. I'm trying to set a boolean parameter to show as NOT required in Swagger UI. It seems the model property attributes dont flow up

I have the following:

app.MapGet("/", ([AsParameters] GetRequestModel request) => "Hello World!");

public class GetRequestModel
{
    [FromQuery(Name = "unassigned_only")]
    [JsonPropertyName("unassigned_only")]
    [SwaggerParameter(Description = "Property description", Required = false)]
    [DefaultValue(false)]
    public bool UnassignedOnly { get; set; }
}

The DefaultValue is displayed on Swagger UI fine, but the SwaggerParameter seems to have no effect. The Description isnt displayed on the Swagger UI and the parameter also still shows as "Required". I also tried using SwaggerSchema attribute, but same result.

@codelovercc
Copy link

I think I'm running in to a similar issue. I'm trying to set a boolean parameter to show as NOT required in Swagger UI. It seems the model property attributes dont flow up

I have the following:

app.MapGet("/", ([AsParameters] GetRequestModel request) => "Hello World!");

public class GetRequestModel
{
    [FromQuery(Name = "unassigned_only")]
    [JsonPropertyName("unassigned_only")]
    [SwaggerParameter(Description = "Property description", Required = false)]
    [DefaultValue(false)]
    public bool UnassignedOnly { get; set; }
}

The DefaultValue is displayed on Swagger UI fine, but the SwaggerParameter seems to have no effect. The Description isnt displayed on the Swagger UI and the parameter also still shows as "Required". I also tried using SwaggerSchema attribute, but same result.

Try change type bool to bool?, then enable nullable reference type for project and Swashbuckle.AspNetCore

@mr-davidc
Copy link

@codelovercc Thanks for the reply.

While changing it the bool? does work, its not really desired since I don't want to handle nullable value on the API side. I simply want to display it as optional on the Swagger UI and default to false.

@codelovercc
Copy link

@mr-davidc Welcome, I'm happy to help.
In my case, with project nullable enabled <Nullable>enable</Nullable>, and

       services.AddSwaggerGen(options =>
        {
            options.SupportNonNullableReferenceTypes();
        });
public class MyDto
{
    public bool Encrypted {get;set;}
}

MyDto.Encrypted will be optional on the Swagger UI.

image

image

@mr-davidc
Copy link

Hi again @codelovercc,

I tried those options you mentioned above, but still my bool properties come through to Swagger UI as required. I really think the [AsParameters] is having an impact here.

I also still have an issue where the endpoint Summary doesn't come through to Swagger UI either, UNLESS I set .WithOpenApi() but then I lose all of my SwaggerParameter descriptions...

public static RouteGroupBuilder GetItemsRoute(this RouteGroupBuilder routeGroupBuilder)
{
    routeGroupBuilder
        .MapGet("/", GetItems)
        .WithSummary("Retrieve a list of items")                    <------- This doesnt display on Swagger UI
        .ProducesProblem(StatusCodes.Status400BadRequest)
        .ProducesProblem(StatusCodes.Status500InternalServerError);

    return routeGroupBuilder;
}

Any other things I can try to fix either of these issues?

@AmielCyber
Copy link

I have the same problem. Using the data annotation [AsParameters] to bind to an object. The data annotations seem to work like [DefaultValue()], but the summary for any property in the object with AsParameters attribute is not shown in SwaggerUI.

@kaloyantihomirov
Copy link

kaloyantihomirov commented Aug 26, 2023

Hi! I want to share my experience as well. I'm also using the data annotation [AsParameters] to bind to an object. The SwaggerParameter seems to have no effect in the Swagger UI.

record Fruit(string Name, int Stock);

record struct CreateFruitModel
    ([FromServices] LinkGenerator Links,
    [FromRoute]
    [SwaggerParameter(Description = "The id of the fruit that will be created", Required = true)] string Id,
    [FromBody] Fruit Fruit);
app.MapPost("/fruit/{id}", handler.CreateFruit)
    .WithName("CreateFruit");
internal class FruitHandler
{
    private readonly ConcurrentDictionary<string, Fruit> _fruit;

    public FruitHandler(ConcurrentDictionary<string, Fruit> fruit)
    {
        _fruit = fruit;
    }

    /// <summary>
    /// Creates a fruit with the given ID, or returns 400 if a fruit with the given ID exists
    /// </summary>
    /// <param name="model">A model for the parameters we need to create a fruit</param>
    /// <response code="201">The fruit was created successfully</response>
    /// <response code="400">A fruit with the given ID already exists in the dict</response>
    /// 
    [ProducesResponseType(typeof(Fruit), 201)]
    [ProducesResponseType(typeof(HttpValidationProblemDetails), 400, "application/problem+json")]
    [Tags("fruit")]
    public IResult CreateFruit([AsParameters] CreateFruitModel model)
     => _fruit.TryAdd(model.Id, model.Fruit)
            ? TypedResults.Created(
                model.Links.GetPathByName("GetFruitById", new { model.Id })
                    ?? "N/A",
                model.Fruit)
            : Results.ValidationProblem(new Dictionary<string, string[]>
            {
                { "id", new[] { "A fruit with this id already exists" } }
            });
}

This is the only thing that I have found to work:

var _fruit = new ConcurrentDictionary<string, Fruit>();

var handler = new FruitHandler(_fruit);

app.MapPost("/fruit/{id}", handler.CreateFruit)
    .WithName("CreateFruit")
    .WithOpenApi(o =>
    {
        o.Parameters[0].Description = "The id of the fruit that will be created";

        return o;
    });

It's not really clear for me which parameters we access with o.Parameters. Here it seems that the collection contains only one parameter: the route id parameter.
image

@BoasHoeven
Copy link

Seems like this feature is still missing, anyone know?

Copy link
Contributor

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

@github-actions github-actions bot added the stale Stale issues or pull requests label Jun 22, 2024
@martincostello martincostello removed the stale Stale issues or pull requests label Jun 22, 2024
@jgarciadelanoceda
Copy link
Contributor

jgarciadelanoceda commented Jun 22, 2024

Hi @andreasuvoss, can you test if this is solved with the addition of #2943.
You can download the latest bits using myGet.
I think that the PR has just done it

@jgarciadelanoceda
Copy link
Contributor

jgarciadelanoceda commented Jun 22, 2024

@kaloyantihomirov
Reviewing this item a bit further I think that you did not use the EnableAnnotations extension method that comes from the nuget package Swashbuckle.AspNetCore.Annotations.
Unfortunuately if you apply it, it throws an error that is only present for this specific scenario. I have got it and soon I will write a PR for this matter

@jgarciadelanoceda
Copy link
Contributor

@kaloyantihomirov can you test using myGet?, my changes are already merged

@andreasuvoss
Copy link
Author

Hi @andreasuvoss, can you test if this is solved with the addition of #2943. You can download the latest bits using myGet. I think that the PR has just done it

I’m not at my computer at the moment, I will try to test this sometime next week, maybe already tomorrow.

@jgarciadelanoceda
Copy link
Contributor

Hi @andreasuvoss, reviewing the issues with MinimalApi, the issue is that if you use AsParameters the ContainerType is not present over the Parameters of the endpoint(The AsParametersArgument class is missing).
I think that we cannot do anything (Let me review it later), apart from recommending you to use the SwaggerParameter attribute

@jgarciadelanoceda
Copy link
Contributor

Hi @martincostello, I wasn't able to get the XML parameters to work when they are members of a AsParameters request.
It's a limitation on the OpenApi provider that Swashbuckle uses under the hood. Should we create an issue in their GitHub repo or as it's supported with the Attributes, it's at least covered

@martincostello
Copy link
Collaborator

Sure, that sounds sensible.

@jgarciadelanoceda
Copy link
Contributor

jgarciadelanoceda commented Aug 20, 2024

It will be supported in dotnet10. dotnet/aspnetcore#56764

@martincostello
Copy link
Collaborator

Closing as external.

@martincostello martincostello closed this as not planned Won't fix, can't repro, duplicate, stale Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants