diff --git a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs index 44bf7dbd3da5..aeef62f85dc0 100644 --- a/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs +++ b/src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs @@ -12,6 +12,7 @@ using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.Json; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -58,9 +59,12 @@ internal sealed class OpenApiSchemaService( TransformSchemaNode = (context, schema) => { var type = context.TypeInfo.Type; - // Fix up schemas generated for IFormFile, IFormFileCollection, Stream, PipeReader and FileContentResult + // Fix up schemas generated for IFormFile, IFormFileCollection, Stream, PipeReader, + // FileContentResult, FileStreamResult, FileContentHttpResult and FileStreamHttpResult // that appear as properties within complex types. - if (type == typeof(IFormFile) || type == typeof(Stream) || type == typeof(PipeReader) || type == typeof(Mvc.FileContentResult)) + if (type == typeof(IFormFile) || type == typeof(Stream) || type == typeof(PipeReader) + || type == typeof(Mvc.FileContentResult) || type == typeof(Mvc.FileStreamResult) + || type == typeof(FileContentHttpResult) || type == typeof(FileStreamHttpResult)) { schema = new JsonObject { diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/JsonTypeInfoExtensionsTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/JsonTypeInfoExtensionsTests.cs index a9ae3255708b..a71b4a6681d1 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/JsonTypeInfoExtensionsTests.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/JsonTypeInfoExtensionsTests.cs @@ -64,6 +64,9 @@ public class Baz [typeof(Stream), "Stream"], [typeof(PipeReader), "PipeReader"], [typeof(FileContentResult), "FileContentResult"], + [typeof(FileStreamResult), "FileStreamResult"], + [typeof(FileContentHttpResult), "FileContentHttpResult"], + [typeof(FileStreamHttpResult), "FileStreamHttpResult"], [typeof(Results, Ok>), "ResultsOfOkOfTodoWithDueDateAndOkOfTodo"], [typeof(Ok), "OkOfTodo"], [typeof(NotFound), "NotFoundOfTodoWithDueDate"], diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs index 05aaeb956848..7cf014d29290 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs @@ -7,6 +7,7 @@ using System.Text.Json.Nodes; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; public partial class OpenApiSchemaServiceTests : OpenApiDocumentServiceTestBase @@ -1055,6 +1056,75 @@ await VerifyOpenApiDocument(builder, document => }); } + [Fact] + public async Task GetOpenApiResponse_HandlesFileStreamResultTypeResponse() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapPost("/filestreamresult", () => { return new FileStreamResult(new MemoryStream(), MediaTypeNames.Application.Octet); }) + .Produces(contentType: MediaTypeNames.Application.Octet); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/filestreamresult"].Operations[HttpMethod.Post]; + var responses = Assert.Single(operation.Responses); + var response = responses.Value; + Assert.True(response.Content.TryGetValue("application/octet-stream", out var mediaType)); + var schema = mediaType.Schema; + Assert.Equal(JsonSchemaType.String, schema.Type); + Assert.Equal("binary", schema.Format); + }); + } + + [Fact] + public async Task GetOpenApiResponse_HandlesFileContentHttpResultTypeResponse() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapPost("/filecontenthttpresult", () => { return TypedResults.File([], MediaTypeNames.Image.Png); }) + .Produces(contentType: MediaTypeNames.Image.Png); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/filecontenthttpresult"].Operations[HttpMethod.Post]; + var responses = Assert.Single(operation.Responses); + var response = responses.Value; + Assert.True(response.Content.TryGetValue("image/png", out var mediaType)); + var schema = mediaType.Schema; + Assert.Equal(JsonSchemaType.String, schema.Type); + Assert.Equal("binary", schema.Format); + }); + } + + [Fact] + public async Task GetOpenApiResponse_HandlesFileStreamHttpResultTypeResponse() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapPost("/filestreamhttpresult", () => { return TypedResults.File(new MemoryStream(), MediaTypeNames.Application.Pdf); }) + .Produces(contentType: MediaTypeNames.Application.Pdf); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var operation = document.Paths["/filestreamhttpresult"].Operations[HttpMethod.Post]; + var responses = Assert.Single(operation.Responses); + var response = responses.Value; + Assert.True(response.Content.TryGetValue("application/pdf", out var mediaType)); + var schema = mediaType.Schema; + Assert.Equal(JsonSchemaType.String, schema.Type); + Assert.Equal("binary", schema.Format); + }); + } + [ApiController] [Produces("application/json")] public class TestController