Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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