Skip to content

Support Content Negotiation in EndpointMetadataApiDescriptionProvider #60590

Open
@adamvandenhoven

Description

@adamvandenhoven

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

For a variety of reasons, I want content negotiation in my API. Specifically I want my routes to be able to return different Schemas for different media types. I know that actually doing the serialization is not something that is going to work out of the box but we already many examples where in Minimal API we can negotiate a JSON or an XML serialization of the object, and its absolutely no different (other than wiring up thing so its not onerous) to return different schemas serialized in different ways.

In my case, I'm working with Fast Endpoints and the Swagger generation is handled by NSwag, but I've tracked the source of "why doesn't this work" to EndpointMetadataApiDescriptionProvider. An example of what I want to have is:

public class GetById : Endpoint<GetUserByIdRequest, UserAggregate>
    public override void Configure()
    {
        Get(GetUserByIdRequest.Route);
        Description(builder =>
        {
            builder
                .Produces<UserRecord>((int)HttpStatusCode.OK, "application/json", "application/vnd.example.user+json")
                .Produces<Summary>((int)HttpStatusCode.OK, "application/vnd.example.summary+json")
                .Produces((int)HttpStatusCode.NotFound)
                .ProducesProblemFE();
        });
    }
}

So my method returns a UserAggregate but the serialization has 3 media types and 2 different schemas, and I know I can convert UserAggregate into then both.

The issue arises here:

if (!supportedResponseTypes.Any(existingResponseType => existingResponseType.StatusCode == apiResponseType.StatusCode))
{
supportedResponseTypes.Add(apiResponseType);
}

The code assumes that you can only have one response for a particular status code, which is not true. What's also annoying is that you would expect these two things to be the same:

public class GetById : Endpoint<GetUserByIdRequest, UserAggregate>
    public override void Configure()
    {
        Get(GetUserByIdRequest.Route);
        Description(builder =>
        {
            builder
                .Produces<UserRecord>((int)HttpStatusCode.OK, "application/json", "application/vnd.example.user+json")
                .Produces((int)HttpStatusCode.NotFound)
                .ProducesProblemFE();
        });
    }
}

and

public class GetById : Endpoint<GetUserByIdRequest, UserAggregate>
    public override void Configure()
    {
        Get(GetUserByIdRequest.Route);
        Description(builder =>
        {
            builder
                .Produces<UserRecord>((int)HttpStatusCode.OK, "application/json")
                .Produces<UserRecord>((int)HttpStatusCode.OK, "application/vnd.example.user+json")
                .Produces((int)HttpStatusCode.NotFound)
                .ProducesProblemFE();
        });
    }
}

but they are not. In the second instance only one media type ends up in the generated swagger. You could argue, "then why do it" but the second is arguably cleaner

Describe the solution you'd like

I would like to have some mechanism whereby I can have multiple response types for a give status code. From what I've looked at in NSwag, for example, it shouldn't be a problem to generate the swagger.

I assume there is some reason other than "you're never going to need it" for this restriction so maybe an explicit opt-in per document or per route is the way to go but I'd really like to see it in there.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions