Seeking guidance on correct usage of Asp.Versioning.OpenApi with full doc comment support #1201
|
While upgrading to version 10.0.0 of this library, I tried to follow the new and revised best practices and examples from Chris Martinez and Microsoft. Everything works fine but when I checked if all my doc comments were shown in Swagger UI, I noticed that the My question now is, what is the "correct" way to configure both Bonus question: Why are currently all doc comments except I followed these setup guides:
Minimal reproduction example (tested on SDK 10.0.201):
#!/usr/bin/env dotnet
#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.OpenApi@2.11.0
#:package Microsoft.AspNetCore.OpenApi@10.0.10
#:package Asp.Versioning.OpenApi@10.0.0-rc.1
#:package Swashbuckle.AspNetCore.SwaggerUI@10.2.3
#:property PublishAot=false
#:property GenerateDocumentationFile=true
#:property InterceptorsNamespaces=$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated
#:property Nullable=enable
#:property NoWarn=$(NoWarn);1591
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
var builder = WebApplication.CreateSlimBuilder(args);
// builder.Services.AddOpenApi();
builder.Services
.AddApiVersioning()
.AddApiExplorer(static setup => setup.GroupNameFormat = "'v'VVV")
.AddOpenApi(static options =>
{
options.Document.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1;
options.Document.AddOperationTransformer<OpenApiDeprecationTransformer>();
});
var app = builder.Build();
app.NewVersionedApi()
.MapGroup("api")
.MapToDoEndpoints();
app.MapOpenApi().WithDocumentPerVersion();
app.UseSwaggerUI(options =>
{
foreach (var description in app.DescribeApiVersions())
{
string name = description.GroupName;
string url = $"/openapi/{name}.json";
options.SwaggerEndpoint(url, name);
}
});
app.Run();
public static class ToDoEndpoints
{
public static IEndpointRouteBuilder MapToDoEndpoints(this IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("ToDo").WithTags("ToDo").HasApiVersion(1);
group.MapGet("{id}", GetToDo);
group.MapGet("plain/{id}", GetPlainToDo);
return endpoints;
}
/// <summary>Summary of the ToDo</summary>
/// <remarks>
/// Remark of the ToDo
///
/// {
/// "title": "Cool feature"
/// }
/// </remarks>
/// <param name="id">The ID param of the ToDo</param>
/// <returns>Returns section of the ToDo</returns>
/// <response code="200">Response code 200 section of the ToDo</response>
/// <response code="404">Response code 404 section of the ToDo</response>
[Obsolete("Use the plain endpoint instead")]
public static Results<Ok<ToDoDto>, NotFound> GetToDo(Guid id)
{
return TypedResults.Ok(new ToDoDto(id, "Example", "Create a minimal repro example."));
}
/// <summary>Summary of the plain ToDo</summary>
/// <remarks>
/// Remark of the plain ToDo
///
/// {
/// "title": "Cool feature"
/// }
/// </remarks>
/// <param name="id">The ID param of the plain ToDo</param>
/// <returns>Returns section of the plain ToDo</returns>
public static ToDoDto GetPlainToDo(Guid id) => new ToDoDto(id, "Example", "Create a minimal repro example.");
}
/// <summary>A ToDo entry</summary>
/// <param name="Id">The unique ID of the ToDo</param>
/// <param name="Title">The title of the todo</param>
/// <param name="Description">The description of the ToDo</param>
/// <example>{"id":"8d186a87-97fb-457f-a07b-955ed08fc3ba","title":"Room cleaning","description":"Clean your room"}</example>
public sealed record ToDoDto(Guid Id, string Title, string Description);
public sealed class OpenApiDeprecationTransformer : IOpenApiOperationTransformer
{
public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken)
{
var obsolete = context.Description.ActionDescriptor.EndpointMetadata.OfType<ObsoleteAttribute>().FirstOrDefault();
if (obsolete is null)
{
return Task.CompletedTask;
}
operation.Deprecated = true;
if (!string.IsNullOrEmpty(obsolete.Message))
{
string obsoleteMessage = $"This endpoint is deprecated: '{obsolete.Message}'";
operation.Description = string.IsNullOrEmpty(operation.Description)
? obsoleteMessage
: $"{obsoleteMessage}\n\n{operation.Description}";
}
return Task.CompletedTask;
}
} |
Replies: 1 comment 3 replies
|
Indeed, you are no longer meant to use One of the key pain points I brought up with the ASP.NET team is the XML Comments support. It was been hacked together and is deeply coupled with The unfortunate consequence is that I've had to fork all of the XML Comment support 😞. Some of the implementation is simple and straight forward, while others are quite complex and have transformations. I tried, and thought I had, covered the core set of things people really care about. I don't have the full background history or knowledge of how or what is actually supported. Some if it didn't really make sense to me; for example, why would you have a There is a work in progress that will go in the next and official release for FWIW, if you're using API Versioning with OpenAPI support, you don't need a custom The |
Indeed, you are no longer meant to use
builder.Services.AddOpenApi(). It is not API version-aware and, unfortunately, the design does not provide for reasonable extensibility. I've had few conversations with Microsoft and there's no simple way around it. I've had to resort to a bunch of Reflection hackery that will be addressed in .NET 11.One of the key pain points I brought up with the ASP.NET team is the XML Comments support. It was been hacked together and is deeply coupled with
Microsoft.AspNetCore.OpenApi, when it shouldn't be IMHO. In fact, there is a larger need that there should an official, general purpose XML Comment library to facilitate such things, but there isn't. The team …