question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Improve OpenAPI/Swagger response metadata for returned IResults

See original GitHub issue

Today, when you return an IResult from a minimal action, you lose all metadata about the type of the response (see https://github.com/dotnet/aspnetcore/pull/33433). You can add the metadata back with something like [ProducesResponseType(typeof(Person), 201], but in many cases the response type could be inferred from the IResult implementation if we designed a way for this to work.

The most obvious idea is to do like ActionResult<TValue> though implicit conversions with interfaces might make this tricky.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

3reactions
DamianEdwardscommented, Jul 20, 2021

Strawman set of extension methods for setting endpoint metadata. Additional context provided in the doc/code comments. These methods and their new supporting types (where required) can also be seen in the playground repo.

using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Http;

public static class OpenApiEndpointConventionBuilderExtensions
{
    /// <summary>
    /// Adds an EndpointNameMetadata item to the Metadata for all endpoints produced by the builder.<br />
    /// The name is used to lookup the endpoint during link generation and as an operationId when generating OpenAPI documentation.<br />
    /// The name must be unique per endpoint.
    /// </summary>
    /// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
    /// <param name="name">The endpoint name.</param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder WithName(this IEndpointConventionBuilder builder, string name)
    {
        // Once Swashbuckle issue is fixed this will set operationId in the swagger doc
        // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2165
        builder.WithMetadata(new EndpointNameMetadata(name));

        return builder;
    }

    /// <summary>
    /// Adds an EndpointGroupNameMetadata item to the Metadata for all endpoints produced by the builder.
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="groupName"></param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder WithGroupName(this IEndpointConventionBuilder builder, string groupName)
    {
        // Swashbuckle uses group name to match APIs with OpenApi documents by default
        // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs#L59
        // Minimal APIs currently doesn't populate the ApiDescription with a group name but we will change that so this can work as intended.
        // Note that EndpointGroupNameMetadata doesn't exist in ASP.NET Core today so we'll have to add that too.
        builder.WithMetadata(new EndpointGroupNameMetadata(groupName));

        return builder;
    }

    /// <summary>
    /// Adds metadata indicating the type of response an endpoint produces.
    /// </summary>
    /// <typeparam name="TResponse">The type of the response.</typeparam>
    /// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
    /// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status200OK.</param>
    /// <param name="contentType">The response content type. Defaults to "application/json"</param>
    /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder Produces<TResponse>(this IEndpointConventionBuilder builder,
        int statusCode = StatusCodes.Status200OK,
        string? contentType = "application/json",
        params string[] additionalContentTypes)
    {
        return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes);
    }

    /// <summary>
    /// Adds metadata indicating the type of response an endpoint produces.
    /// </summary>
    /// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
    /// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status200OK.</param>
    /// <param name="responseType">The type of the response. Defaults to null.</param>
    /// <param name="contentType">The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null.</param>
    /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder Produces(this IEndpointConventionBuilder builder,
        int statusCode = StatusCodes.Status200OK,
        Type? responseType = null,
        string? contentType = null,
        params string[] additionalContentTypes)
    {
        if (responseType is Type && string.IsNullOrEmpty(contentType))
        {
            contentType = "application/json";
        }

        builder.WithMetadata(new ProducesMetadataAttribute(responseType, statusCode, contentType, additionalContentTypes));

        return builder;
    }

    /// <summary>
    /// Adds metadata indicating that the endpoint produces a Problem Details response.
    /// </summary>
    /// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
    /// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status500InternalServerError.</param>
    /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder ProducesProblem(this IEndpointConventionBuilder builder,
        int statusCode = StatusCodes.Status500InternalServerError,
        string contentType = "application/problem+json")
    {
        return Produces<ProblemDetails>(builder, statusCode, contentType);
    }

    /// <summary>
    /// Adds metadata indicating that the endpoint produces a Problem Details response including validation errors.
    /// </summary>
    /// <param name="builder">The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</param>
    /// <param name="statusCode">The response status code. Defatuls to StatusCodes.Status400BadRequest.</param>
    /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
    /// <returns>The Microsoft.AspNetCore.Builder.IEndpointConventionBuilder.</returns>
    public static IEndpointConventionBuilder ProducesValidationProblem(this IEndpointConventionBuilder builder,
        int statusCode = StatusCodes.Status400BadRequest,
        string contentType = "application/problem+json")
    {
        return Produces<HttpValidationProblemDetails>(builder, statusCode, contentType);
    }
}

Design considerations

Further details that were considered during the design of this proposal:

  • Having specific methods for each response type that could be produced by the methods on Microsoft.AspNetCore.Http.Results, e.g. ProducesCreated, ProducesConflict, ProducesNotFound, etc. As the eventual goal is for the experience to determine the correct information RE what endpoints produce and set the relevant metadata by default, thus requiring these methods to not be required to be called in the vast majority of cases, it was decided that we shouldn’t create such a large set of API surface area only to have it not be used (generally) in the future.
  • After the methods were reduced to just a minimal set, as detailed in the previous point, it was apparent that specifying that an API produces Problem Details required an extremely verbose and likely unintuitive call, i.e. Produces<ProblemDetails>(500, "application/problem+json") and Produces<HttpValidationProblemDetails>(400, "application/problem+json"). For this reason it was decided that higher level methods for problem details should be included, e.g. ProducesProblem, ProducesValidationProblem. These offer a nice symmetry with the Results.Problem and Results.ValidationProblem results methods too.
1reaction
DamianEdwardscommented, Jul 19, 2021

Also I do think at some point we’ll enable hopefully much of this as a convention/automatically so that one doesn’t need to describe the response shape manually at all, e.g. via a source generator.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Create responses in Minimal API applications
Learn how to create responses for minimal APIs in ASP. ... This alternative is better than returning IResult because the generic union types ......
Read more >
Open API support for ASP.NET Core Minimal API
This article will discuss about implementing Open API (Swagger) for ASP.NET Core 6.0 minimal API. Today I saw one video from Maria Naggaga ......
Read more >
Enriched Web API Documentation using Swagger/OpenAPI in ...
Testing tools to execute API requests and validate responses on the fly. ... Possible actions to improve the default OpenAPI documentation.
Read more >
Handling API validation with OpenAPI (Swagger) ...
Handling API validation with OpenAPI (Swagger) documents in NodeJS. ... It has two responses, a 200 that returns an array of records, ...
Read more >
In Python's FastAPI autogenerated OpenAPI/Swagger ...
FastAPI generates automatic swagger/openapi documentation. In the tutorial at https://fastapi.tiangolo.com/tutorial/response-status-code there's ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found