Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/OpenApi/sample/Controllers/XmlController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public string Get()

/// <param name="name">The name of the person.</param>
/// <response code="200">Returns the greeting.</response>
[HttpGet]
[HttpGet("{name}")]
public string Get1(string name)
{
return $"Hello, {name}!";
Expand Down
2 changes: 2 additions & 0 deletions src/OpenApi/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Microsoft.AspNetCore.OpenApi.OpenApiDocumentTransformerContext.GetOrCreateSchema
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Document.get -> Microsoft.OpenApi.OpenApiDocument?
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.Document.init -> void
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.GetOrCreateSchemaAsync(System.Type! type, Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription? parameterDescription = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.OpenApi.OpenApiSchema!>!
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.AllDescriptions.get -> System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription!>!
Microsoft.AspNetCore.OpenApi.OpenApiOperationTransformerContext.AllDescriptions.init -> void
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Document.get -> Microsoft.OpenApi.OpenApiDocument?
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.Document.init -> void
Microsoft.AspNetCore.OpenApi.OpenApiSchemaTransformerContext.GetOrCreateSchemaAsync(System.Type! type, Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription? parameterDescription = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.OpenApi.OpenApiSchema!>!
Expand Down
46 changes: 39 additions & 7 deletions src/OpenApi/src/Services/OpenApiDocumentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,24 +269,32 @@ private async Task<Dictionary<HttpMethod, OpenApiOperation>> GetOperationsAsync(
CancellationToken cancellationToken)
{
var operations = new Dictionary<HttpMethod, OpenApiOperation>();
foreach (var description in descriptions)
foreach (var httpMethodDescriptions in descriptions.GroupBy(d => d.GetHttpMethod()))
{
var operation = await GetOperationAsync(description, document, scopedServiceProvider, schemaTransformers, cancellationToken);
// `description` is the first description for a given Route + HttpMethod.
// There may be additional descriptions if the endpoint has additional definitions
// with different [Consumes] definitions. We merge in the bodies of these additional endpoints,
// but currently don't merge any other parts of the definition.
IReadOnlyList<ApiDescription> allDescriptions = [.. httpMethodDescriptions];
var description = allDescriptions.First();

var operation = await GetOperationAsync(allDescriptions, document, scopedServiceProvider, schemaTransformers, cancellationToken);
operation.Metadata ??= new Dictionary<string, object>();
operation.Metadata.Add(OpenApiConstants.DescriptionId, description.ActionDescriptor.Id);

var operationContext = new OpenApiOperationTransformerContext
{
DocumentName = documentName,
Description = description,
AllDescriptions = [.. allDescriptions],
ApplicationServices = scopedServiceProvider,
Document = document,
SchemaTransformers = schemaTransformers
};

_operationTransformerContextCache.TryAdd(description.ActionDescriptor.Id, operationContext);

if (description.GetHttpMethod() is not { } method)
if (httpMethodDescriptions.Key is not { } method)
{
// Skip unsupported HTTP methods
continue;
Expand All @@ -303,8 +311,9 @@ private async Task<Dictionary<HttpMethod, OpenApiOperation>> GetOperationsAsync(

// Apply any endpoint-specific operation transformers registered via
// the AddOpenApiOperationTransformer extension method.
var endpointOperationTransformers = description.ActionDescriptor.EndpointMetadata
.OfType<DelegateOpenApiOperationTransformer>();
var endpointOperationTransformers = allDescriptions
.SelectMany(d => d.ActionDescriptor.EndpointMetadata
.OfType<DelegateOpenApiOperationTransformer>());
foreach (var endpointOperationTransformer in endpointOperationTransformers)
{
await endpointOperationTransformer.TransformAsync(operation, operationContext, cancellationToken);
Expand All @@ -314,12 +323,13 @@ private async Task<Dictionary<HttpMethod, OpenApiOperation>> GetOperationsAsync(
}

private async Task<OpenApiOperation> GetOperationAsync(
ApiDescription description,
IReadOnlyList<ApiDescription> descriptions,
OpenApiDocument document,
IServiceProvider scopedServiceProvider,
IOpenApiSchemaTransformer[] schemaTransformers,
CancellationToken cancellationToken)
{
var description = descriptions.First();
var tags = GetTags(description, document);
var operation = new OpenApiOperation
{
Expand All @@ -328,9 +338,31 @@ private async Task<OpenApiOperation> GetOperationAsync(
Description = GetDescription(description),
Responses = await GetResponsesAsync(document, description, scopedServiceProvider, schemaTransformers, cancellationToken),
Parameters = await GetParametersAsync(document, description, scopedServiceProvider, schemaTransformers, cancellationToken),
RequestBody = await GetRequestBodyAsync(document, description, scopedServiceProvider, schemaTransformers, cancellationToken),
Tags = tags,
};

foreach (var bodyDescription in descriptions)
{
var requestBody = await GetRequestBodyAsync(document, bodyDescription, scopedServiceProvider, schemaTransformers, cancellationToken);
if (operation.RequestBody is null)
{
operation.RequestBody = requestBody;
}
else if (requestBody is not null)
{
// Merge additional accepted content types that are defined by additional endpoint descriptions.
// `RequestBody.Content` produced by `GetRequestBodyAsync` is never null.
var existingContent = operation.RequestBody.Content!;
foreach (var additionalContent in requestBody.Content!)
{
if (!existingContent.ContainsKey(additionalContent.Key))
{
existingContent.Add(additionalContent);
}
}
}
}

return operation;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ public sealed class OpenApiOperationTransformerContext
public required string DocumentName { get; init; }

/// <summary>
/// Gets the API description associated with target operation.
/// Gets the primary API description associated with target operation.
/// </summary>
public required ApiDescription Description { get; init; }

/// <summary>
/// Gets all API descriptions that were merged to create the target operation.
/// </summary>
public required IReadOnlyList<ApiDescription> AllDescriptions { get; init; }

/// <summary>
/// Gets the application services associated with the current document the target operation is in.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,11 @@
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "query",
"description": "The name of the person.",
"schema": {
"type": "string"
}
}
],
"summary": "A summary of the action.",
"description": "A description of the action.",
"responses": {
"200": {
"description": "Returns the greeting.",
"description": "OK",
"content": {
"text/plain": {
"schema": {
Expand Down Expand Up @@ -230,6 +222,46 @@
}
}
}
},
"/Xml/{name}": {
"get": {
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "path",
"description": "The name of the person.",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Returns the greeting.",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": {
Expand Down Expand Up @@ -394,4 +426,4 @@
"name": "Xml"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,11 @@
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "query",
"description": "The name of the person.",
"schema": {
"type": "string"
}
}
],
"summary": "A summary of the action.",
"description": "A description of the action.",
"responses": {
"200": {
"description": "Returns the greeting.",
"description": "OK",
"content": {
"text/plain": {
"schema": {
Expand Down Expand Up @@ -230,6 +222,46 @@
}
}
}
},
"/Xml/{name}": {
"get": {
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "path",
"description": "The name of the person.",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Returns the greeting.",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": {
Expand Down Expand Up @@ -394,4 +426,4 @@
"name": "Xml"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,19 +448,11 @@
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "query",
"description": "The name of the person.",
"schema": {
"type": "string"
}
}
],
"summary": "A summary of the action.",
"description": "A description of the action.",
"responses": {
"200": {
"description": "Returns the greeting.",
"description": "OK",
"content": {
"text/plain": {
"schema": {
Expand Down Expand Up @@ -530,6 +522,46 @@
}
}
},
"/Xml/{name}": {
"get": {
"tags": [
"Xml"
],
"parameters": [
{
"name": "name",
"in": "path",
"description": "The name of the person.",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Returns the greeting.",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
},
"text/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/schemas-by-ref/typed-results": {
"get": {
"tags": [
Expand Down Expand Up @@ -2037,4 +2069,4 @@
"name": "Test"
}
]
}
}
Loading
Loading