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

Automatically set OpenAPI endpoint metadata for minimal APIs #34544

Open
DamianEdwards opened this issue Jul 20, 2021 · 11 comments
Open

Automatically set OpenAPI endpoint metadata for minimal APIs #34544

DamianEdwards opened this issue Jul 20, 2021 · 11 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-minimal-actions Controller-like actions for endpoint routing Needs: Design This issue requires design work before implementating. Priority:1 Work that is critical for the release, but we could probably ship without

Comments

@DamianEdwards
Copy link
Member

DamianEdwards commented Jul 20, 2021

Rather than having to manually specify the endpoint metadata associated with describing the response types a minimal API produces, it would be preferable to have these details automatically added based on what the minimal API actually does (its implemention) and/or by some convention based on how it's declared.

  • For anonymous delegates, the name could be automatically set via a convention based on which HTTP verb it responds to, the path it's mapped to, and the parameters it accepts from the request, e.g. app.MapGet("/hello/{name}", (string name) => $"Hello {name}"); might get an auto-generated name of GetHelloByName.
  • For IResult returning endpoint delegates, the status codes, content types, and response types the endpoint returns could be determined by analyzing the method body implementation and detecting the return paths and determining the specific details of what the IResult does when executed, e.g. by annotations on the IResult-implementing type itself, or some other mechanism
  • The discovered details could be written to a source-generated manifest that is embedded in the project assembly and discovered at application startup automatically (e.g. via an IHostingStartup & IStartupFilter ) and is then used to match the details with the registered endpoints.

This is scoped to post-.NET 6 at this time, but could potentially be delivered before .NET 7 if it only involves an update in the tooling/SDK rather than an update in the ASP.NET Core shared framework.

@ghost
Copy link

ghost commented Jul 20, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@DamianEdwards DamianEdwards changed the title POST .NET 6 Automatically set endpoint metadata based on endpoint declaration, implementation, and/or convention POST .NET 6 Automatically set endpoint metadata for minimal APIs based on endpoint declaration, implementation, and/or convention Jul 20, 2021
@DamianEdwards DamianEdwards changed the title POST .NET 6 Automatically set endpoint metadata for minimal APIs based on endpoint declaration, implementation, and/or convention (Post .NET 6) Automatically set endpoint metadata for minimal APIs based on endpoint declaration, implementation, and/or convention Jul 20, 2021
@halter73 halter73 added the Needs: Design This issue requires design work before implementating. label Nov 3, 2021
@rafikiassumani-msft rafikiassumani-msft added Cost:M Priority:1 Work that is critical for the release, but we could probably ship without labels Jan 25, 2022
@adityamandaleeka adityamandaleeka added area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed area-runtime labels Jul 28, 2022
@captainsafia captainsafia changed the title (Post .NET 6) Automatically set endpoint metadata for minimal APIs based on endpoint declaration, implementation, and/or convention Automatically et OpenAPI endpoint metadata for minimal APIs Aug 24, 2022
@captainsafia captainsafia changed the title Automatically et OpenAPI endpoint metadata for minimal APIs Automatically set OpenAPI endpoint metadata for minimal APIs Aug 24, 2022
@captainsafia
Copy link
Member

captainsafia commented Aug 24, 2022

For a given endpoint, we need to be able describe the following attributes via OpenAPI:

Deriving request parameters (route params, query params, header params) is already done, given that these parameter types are easy to infer since they are all parseable from strings. We run into some issues with representing things like DateOnly and new primitive types from time to time, but there's no foundational issue here.

Deriving the request body is marginally harder than describing request parameters because we need to be able to determine the schema for a given property.

Deriving the response body is harder still since there can be multiple responses from a single endpoint and there are multiple facets to annotate as mentioned above (status code, content type, response schema).

For both request body and response body, the IEndpointMetadataProvider and IEndpointParameterMetadataProvider API provide vectors for setting this metadata, however, it's not automatic and requires user intervention.

Deriving authentication config will likely depend on the work done in #39761. IMO, this is probably the most important one for us to figure out at the moment given that there's really no way to configure this in the framework at the moment. We don't have a Produces or Accepts for authentication info.

With that in mind, we can either solve the problem for automatically setting authentication config separately from setting request/response body information or we can do user the same hammer for both problems.

#34543 is related to this work and lends itself to a "static analysis"-based approach for automatically determine the right annotations to put in for requests/responses. #39761 identifies the challenge with automatically generating auth annotations.

With the landscape laid out this way, I'm inclined to shake out the static analysis approach (analyzer/source generator/etc.) as a dual-purpose solution for both the authentication problem and the request/response problem. Also, the static analysis approach would provide a vector for solving #39927 (which has quite a few thumbs up!)

Oh, the 'automatically generating OperationId" problem is certainly a valid one to solve although, IMO, it's not as important as solving the other two until we make considerable headway in the client generation area.

@DamianEdwards
Copy link
Member Author

Worth noting that we added TypedResults and union results via Results<TResult1, TResultN, ...> in .NET 7 to enable automatic setting of OpenAPI related metadata for response codes/bodies which at least partially addresses this issue (albeit with a required app code change).

@captainsafia
Copy link
Member

Worth noting that we added TypedResults and union results via Results<TResult1, TResultN, ...> in .NET 7 to enable automatic setting of OpenAPI related metadata for response codes/bodies which at least partially addresses this issue (albeit with a required app code change).

Ah, good point! I had captured the metadata provider strategy above but not this one.

And this thread has made me realize that we have quite a few different ways to annotate responses. I've captured an item to make sure we document these in #43145.

@captainsafia
Copy link
Member

As I've been playing around with the static analysis for OpenAPI generation strategy here, I'm noting the challenge of converging schema that can be deduced by static analysis and schema that must be derived by examining metadata.

Currently, we can derive annotation responses in the following ways (from lowest precedence to highest precedence):

  • Annotations derived by default when examining the response type of an endpoint
  • Annotations derived by an IEndpointMetadataProvider (TypedResults are implicitly captured in this category since they use the IEndpointMetadataProvider infrastructure)
  • Annotations provided by the user via a Produces extension method or attribute

Assuming we had a static analysis phase, we would be able to derive the following at compile-time:

  • Derive annotations by examining the response type of an endpoint
  • Derive annotations by examining the XML documentation on an endpoint where applicable

And the following would be derived at runtime:

  • Annotations derived by an IEndpointMetadataProvider (TypedResults are implicitly captured in this category since they use the IEndpointMetadataProvider infrastructure)
  • Annotations provided by the user via a Produces extension method or attribute

This flow accounts for premise, so we consider annotations from XML documents to be lower precedence than annotations from IEndpointMetadataProviders. Note: although the above is in reference to responses, the same hierarchy exists for requests.

@DamianEdwards
Copy link
Member Author

Crazy thought, although not sure how valuable, perhaps annotations from IEndpointMetadataProvider could actually be captured at compile/build-time, similar to how the EF Core and scaffolding CLIs captures app/CI details. Not sure it gains anything in this scenario but .NET has a history of this style of extensibility in the past.

@captainsafia
Copy link
Member

@DamianEdwards has been working on a scenario that leverages the new ProblemDetailsService to configure response types that are applied to all endpoints in an application or all endpoints in a group.

This is an inference problem that is a little closer to the "introspect auth" problem than it is the "introspect an endpoint" problem since it requires taking knowledge derived at the app-level and including it in each's groups annotation. OpenAPI uses the components property as a category in which these cross-cutting and reusable objects are stored (like response schemas, authentication schemes, etc).

This behavior exposes another notable gap in our OpenAPI-implementations which is that they are highly operation specific, we don't rely expose any primitives that allow the user to provide app-level annotations and generally rely on external dependencies for that. This has generally been a fine design choice, but the ProblemDetails case and the "introspect auth" scenario indicate that we might be outgrowing that restriction.

@amcasey amcasey added the area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc label Jun 2, 2023
@captainsafia captainsafia removed the area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Jun 6, 2023
@captainsafia captainsafia removed this from the .NET 8 Planning milestone Mar 1, 2024
@captainsafia captainsafia added this to the .NET 9 Planning milestone Mar 1, 2024
@zyofeng
Copy link

zyofeng commented Jun 4, 2024

Hi guys

Not sure if this is the right place to report the issue, but in .net 8 minimal api, it seems the metadata provider is not working with FileContentHttpResult


        group.MapGet("test",  () =>
                TypedResults.File([])
            )

The following is produced in NSwag

"responses": {
      "200": {
          "description": ""
      }
  }

and I have to explicitly add the Produces() to make it work.

        group.MapPost("test",  () =>
                TypedResults.File([])
            )
            .Produces<FileContentResult>()

@halter73
Copy link
Member

halter73 commented Jun 10, 2024

FileContentHttpResult is not intended to add any metadata to your response. It does not implement IEndpointMetadataProvider because we don't know just based on the type whether the default content-type of
application/octet-stream was overridden or even if the status code was changed from 200 before the FileContentHttpResult was returned. We decided to be very conservative and not infer more than we really know.

.Produces<FileContentResult>()

I don't think there's a reason to ever do that. Unless you want the OpenAPI doc to say that will return an
application/json response with a "FileContents" property. I think you would want something like the following instead:

.Produces(200, contentType: "application/octet-stream")

@zyofeng
Copy link

zyofeng commented Jun 10, 2024

According to Mozilla, "application/octet-stream" is for generic binary data whose true type is unknown, I feel like it's safe to infer it as a fallback since we still have the ability to overwrite it with a specific content type using Produces(200, xxxx)
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types

.Produces<FileContentResult>()

Generates the correct metadata as far as I can see.
image

@dse-copsfs
Copy link

I like @zyofeng's proposal with the fallback solution. Please consider it for planning the next milestone, it would be beneficial for our project, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-minimal-actions Controller-like actions for endpoint routing Needs: Design This issue requires design work before implementating. Priority:1 Work that is critical for the release, but we could probably ship without
Projects
None yet
Development

No branches or pull requests

8 participants