From 25ea78ea72bf6c0d07e445dac7a862b1d6ae4f12 Mon Sep 17 00:00:00 2001 From: Havunen Date: Sun, 25 Feb 2024 13:20:29 +0200 Subject: [PATCH] Add support for HttpResults https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2595 for https://github.com/Havunen/DotSwashbuckle/issues/3 --- .../SchemaGenerator/SchemaGenerator.cs | 1 - .../ApiDescriptionExtensions.cs | 3 +- .../SwaggerGenerator/SchemaRepository.cs | 1 - .../SwaggerGenerator/SwaggerGenerator.cs | 59 ++++++++++++++++++- .../Controllers/CrudActionsController.cs | 43 +++++++++++++- 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs index f845b80983..5b80f2fabb 100644 --- a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs +++ b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs @@ -3,7 +3,6 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Data.SqlTypes; using System.Globalization; using System.Linq; using System.Reflection; diff --git a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs index e03d56502a..3d8cd7cb34 100644 --- a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs +++ b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Collections.Generic; using System.Reflection; using Microsoft.AspNetCore.Mvc.ApiExplorer; diff --git a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs index fb6d84bc30..c1ca11c289 100644 --- a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs +++ b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; diff --git a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs index 9744df222b..3e5be2c827 100644 --- a/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs +++ b/src/DotSwashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs @@ -10,8 +10,11 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.OpenApi.Models; using DotSwashbuckle.AspNetCore.Swagger; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Metadata; using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Mvc.Controllers; namespace DotSwashbuckle.AspNetCore.SwaggerGen { @@ -416,7 +419,7 @@ private OpenApiParameter GenerateParameter( apiParameter.PropertyInfo(), apiParameter.ParameterInfo(), apiParameter.RouteInfo - ) : new OpenApiSchema() {Type = "string"}; + ) : new OpenApiSchema() { Type = "string" }; var parameter = new OpenApiParameter { @@ -616,11 +619,51 @@ private OpenApiSchema GenerateSchemaFromFormParameters( }; } + private IList GetResponseTypes( + ApiDescription apiDescription + ) + { + var supportedResponseTypes = new List(apiDescription.SupportedResponseTypes); + + if (apiDescription.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) + { + var returnType = UnwrapTask(controllerActionDescriptor.MethodInfo.ReturnType); + + if (typeof(IEndpointMetadataProvider).IsAssignableFrom(returnType)) + { + var populateMetadataMethod = returnType.GetMethod("Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider.PopulateMetadata", BindingFlags.Static | BindingFlags.NonPublic); + + if (populateMetadataMethod != null) + { + var endpointBuilder = new MetadataEndpointBuilder(); + populateMetadataMethod.Invoke(null, [controllerActionDescriptor.MethodInfo, endpointBuilder]); + + var responseTypes = endpointBuilder.Metadata.Cast().ToList(); + + foreach (var responseType in responseTypes) + { + supportedResponseTypes.Add( + new ApiResponseType() + { + IsDefaultResponse = false, + Type = responseType.Type, + StatusCode = responseType.StatusCode, + ApiResponseFormats = responseType.ContentTypes.Select(contentType => new ApiResponseFormat { MediaType = contentType }).ToList() + }); + } + } + } + + } + + return supportedResponseTypes; + } + private OpenApiResponses GenerateResponses( ApiDescription apiDescription, SchemaRepository schemaRepository) { - var supportedResponseTypes = apiDescription.SupportedResponseTypes + var supportedResponseTypes = GetResponseTypes(apiDescription) .DefaultIfEmpty(new ApiResponseType { StatusCode = 200 }); var responses = new OpenApiResponses(); @@ -657,7 +700,7 @@ private OpenApiResponse GenerateResponse( private IEnumerable InferResponseContentTypes(ApiDescription apiDescription, ApiResponseType apiResponseType) { // If there's no associated model, return an empty list (i.e. no content) - if (apiResponseType.ModelMetadata == null) return Enumerable.Empty(); + if (apiResponseType.Type == null || apiResponseType.Type == typeof(void)) return Enumerable.Empty(); // If there's content types explicitly specified via ProducesAttribute, use them var explicitContentTypes = apiDescription.CustomAttributes().OfType() @@ -727,5 +770,15 @@ private OpenApiMediaType CreateResponseMediaType(Type type, SchemaRepository sch new KeyValuePair("5\\d{2}", "Server Error"), new KeyValuePair("default", "Error") }; + + private static Type UnwrapTask(Type type) + => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>) + ? type.GetGenericArguments()[0] + : type; + + private sealed class MetadataEndpointBuilder : EndpointBuilder + { + public override Endpoint Build() => null!; + } } } diff --git a/test/WebSites/Basic/Controllers/CrudActionsController.cs b/test/WebSites/Basic/Controllers/CrudActionsController.cs index 56784c267f..cf7c0c1e1e 100644 --- a/test/WebSites/Basic/Controllers/CrudActionsController.cs +++ b/test/WebSites/Basic/Controllers/CrudActionsController.cs @@ -1,6 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; namespace Basic.Controllers @@ -106,6 +110,43 @@ public IActionResult GetDoc([FromQuery] IChild query) { return null; } + + [HttpPost("GetDoc2")] + public async Task>> GetAll(GetData.Query query) + { + return null; + } + + /// Request the forecast of a specific day. + /// Date of the requested forecast. + [HttpGet("weather/{date}")] + public Results, NotFound> GetWeather(DateOnly date) + { + return TypedResults.Ok(new WeatherForecast()); + } + + /// Request the forecast of a specific day. + /// Date of the requested forecast. + [HttpGet("weather/async/{date}")] + public async Task, NotFound>> GetWeatherAsync(DateOnly date) + { + return TypedResults.Ok(await Task.FromResult(new WeatherForecast())); + } + } + + public class WeatherForecast + { + public DateOnly Date { get; set; } + public int TemperatureC { get; set; } + public string? Summary { get; set; } + } + + public class GetData + { + public class Query + { + public int MyParameter { get; set; } + } } /// The parent.