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

Add Description to ProducesResponseType (and others) for better OpenAPI document creation #58193

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1f433b6
Started adding Description to several attributes
sander1095 Mar 17, 2024
364e278
Merge branch 'dotnet:main' into main
sander1095 Apr 30, 2024
d71759c
Add new properties to unshipped apis
sander1095 Apr 30, 2024
019bd17
Some more progress of adding Description in some places
sander1095 Apr 30, 2024
e7885ab
Merge branch 'dotnet:main' into main
sander1095 May 10, 2024
4b75efc
Merge branch 'dotnet:main' into main
sander1095 Aug 16, 2024
6da811a
Small improvements based on API review comments
sander1095 Aug 16, 2024
951676d
Added missing modifier to property
sander1095 Aug 16, 2024
3bdbe26
Make changes in unshipped.txt so the http project builds
sander1095 Aug 16, 2024
442d101
Add Description to OpenApiRouteHandlerBuilderExtensions and extra con…
sander1095 Aug 16, 2024
f7416da
Changed code to follow overload rules and fix compile issues
sander1095 Aug 16, 2024
443babd
Fix minor typo
sander1095 Sep 16, 2024
7700ccc
Remove code from OpenApiRouteHandlerBUilderExtensions based on Safiaś…
sander1095 Sep 16, 2024
9614111
Fix incorrect XML Comment
sander1095 Sep 16, 2024
365c7d6
Fixed some more Public API issues
sander1095 Sep 16, 2024
6964a4d
Merge branch 'dotnet:main' into main
sander1095 Sep 16, 2024
a384d69
Add unit test
sander1095 Sep 16, 2024
c071c35
Add some more unit tests
sander1095 Sep 16, 2024
aa366b7
Remove unnecessary set from interface
sander1095 Sep 17, 2024
2d1314f
Merge branch 'dotnet:main' into main
sander1095 Sep 17, 2024
4e9fcd8
Merge branch 'dotnet:main' into main
sander1095 Oct 1, 2024
24128f6
Remove unnecessary change in public api shipped file
sander1095 Oct 1, 2024
bb3e442
Also update unshipped file so the build succeeds!
sander1095 Oct 1, 2024
d5dae48
Apply the new Description in response models
sander1095 Oct 9, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public interface IProducesResponseTypeMetadata
/// </summary>
int StatusCode { get; }

/// <summary>
/// Gets the description of the response.
/// </summary>
string? Description { get; }

/// <summary>
/// Gets the content types supported by the metadata.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ private ProducesResponseTypeMetadata(int statusCode, Type? type, IEnumerable<str
/// </summary>
public int StatusCode { get; private set; }

/// <summary>
/// Gets or sets the description of the response.
/// </summary>
public string? Description { get; set; }

/// <summary>
/// Gets or sets the content types associated with the response.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ Microsoft.AspNetCore.Http.Metadata.IParameterBindingMetadata.HasTryParse.get ->
Microsoft.AspNetCore.Http.Metadata.IParameterBindingMetadata.IsOptional.get -> bool
Microsoft.AspNetCore.Http.Metadata.IParameterBindingMetadata.Name.get -> string!
Microsoft.AspNetCore.Http.Metadata.IParameterBindingMetadata.ParameterInfo.get -> System.Reflection.ParameterInfo!
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string?
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void
Microsoft.AspNetCore.Http.Metadata.IProducesResponseTypeMetadata.Description.get -> string?
2 changes: 1 addition & 1 deletion src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ private static RequestDelegateFactoryContext CreateFactoryContext(RequestDelegat
var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices ?? EmptyServiceProvider.Instance;
var endpointBuilder = options?.EndpointBuilder ?? new RdfEndpointBuilder(serviceProvider);
var jsonSerializerOptions = serviceProvider.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
var formDataMapperOptions = new FormDataMapperOptions();;
var formDataMapperOptions = new FormDataMapperOptions();

var factoryContext = new RequestDelegateFactoryContext
{
Expand Down
5 changes: 5 additions & 0 deletions src/Mvc/Mvc.Abstractions/src/ApiExplorer/ApiResponseType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public class ApiResponseType
/// </remarks>
public Type? Type { get; set; }

/// <summary>
/// Gets or sets the description of the response.
/// </summary>
public string? Description { get; set; }

/// <summary>
/// Gets or sets the HTTP response status code.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType.Description.get -> string?
Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType.Description.set -> void
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
Expand Down Expand Up @@ -60,6 +60,8 @@ public class CustomResponseTypeAttribute : Attribute, IApiResponseMetadataProvid

public int StatusCode { get; set; }

public string Description { get; set; }

public void SetContentTypes(MediaTypeCollection contentTypes)
{
}
Expand Down
3 changes: 3 additions & 0 deletions src/Mvc/Mvc.ApiExplorer/src/ApiResponseTypeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,14 @@ internal static Dictionary<int, ApiResponseType> ReadResponseMetadata(

var statusCode = metadataAttribute.StatusCode;

var description = metadataAttribute.Description;

var apiResponseType = new ApiResponseType
{
Type = metadataAttribute.Type,
StatusCode = statusCode,
IsDefaultResponse = metadataAttribute is IApiDefaultResponseMetadataProvider,
Description = description
};

if (apiResponseType.Type == typeof(void))
Expand Down
2 changes: 2 additions & 0 deletions src/Mvc/Mvc.ApiExplorer/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType.Description.get -> string? (forwarded, contained in Microsoft.AspNetCore.Mvc.Abstractions)
Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType.Description.set -> void (forwarded, contained in Microsoft.AspNetCore.Mvc.Abstractions)
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,8 @@ public void GetApiDescription_PopulatesResponseInformation_WhenSetByFilter(strin
var action = CreateActionDescriptor(methodName);
var filter = new ContentTypeAttribute("text/*")
{
Type = typeof(Order)
Type = typeof(Order),
Description = "Example"
};

action.FilterDescriptors = new List<FilterDescriptor>
Expand All @@ -1124,6 +1125,7 @@ public void GetApiDescription_PopulatesResponseInformation_WhenSetByFilter(strin
Assert.NotNull(responseTypes.ModelMetadata);
Assert.Equal(200, responseTypes.StatusCode);
Assert.Equal(typeof(Order), responseTypes.Type);
Assert.Equal("Example", responseTypes.Description);

foreach (var responseFormat in responseTypes.ApiResponseFormats)
{
Expand Down Expand Up @@ -2874,6 +2876,8 @@ public ContentTypeAttribute(string mediaType)

public Type Type { get; set; }

public string Description { get; set; }

public void SetContentTypes(MediaTypeCollection contentTypes)
{
contentTypes.Clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public interface IApiResponseMetadataProvider : IFilterMetadata
/// </summary>
Type? Type { get; }

/// <summary>
/// Gets the description of the response.
/// </summary>
string? Description { get; }

/// <summary>
/// Gets the HTTP status code of the response.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Mvc/Mvc.Core/src/ProducesAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public ProducesAttribute(string contentType, params string[] additionalContentTy
/// <inheritdoc />
public Type? Type { get; set; }

/// <inheritdoc />
public string? Description { get; set; }

/// <summary>
/// Gets or sets the supported response content types. Used to set <see cref="ObjectResult.ContentTypes"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Mvc.ApiExplorer;
Expand Down Expand Up @@ -39,6 +39,11 @@ public ProducesDefaultResponseTypeAttribute(Type type)
/// </summary>
public int StatusCode { get; }

/// <summary>
/// Gets or sets the description of the response.
/// </summary>
public string? Description { get; set; }

/// <inheritdoc />
void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
{
Expand Down
7 changes: 6 additions & 1 deletion src/Mvc/Mvc.Core/src/ProducesResponseTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Mvc.ApiExplorer;
Expand Down Expand Up @@ -87,6 +87,11 @@ public ProducesResponseTypeAttribute(Type type, int statusCode, string contentTy
// Internal for testing
internal MediaTypeCollection? ContentTypes => _contentTypes;

/// <summary>
/// Gets or sets the description of the response.
/// </summary>
public string? Description { get; set; }

/// <inheritdoc />
void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
{
Expand Down
7 changes: 7 additions & 0 deletions src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
#nullable enable
*REMOVED*virtual Microsoft.AspNetCore.Mvc.ControllerBase.Problem(string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null) -> Microsoft.AspNetCore.Mvc.ObjectResult!
*REMOVED*virtual Microsoft.AspNetCore.Mvc.ControllerBase.ValidationProblem(string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null, Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary? modelStateDictionary = null) -> Microsoft.AspNetCore.Mvc.ActionResult!
Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider.Description.get -> string?
Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.get -> string?
Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.set -> void
Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute.Description.get -> string?
Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute.Description.set -> void
Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute.Description.get -> string?
Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute.Description.set -> void
virtual Microsoft.AspNetCore.Mvc.ControllerBase.Problem(string? detail, string? instance, int? statusCode, string? title, string? type) -> Microsoft.AspNetCore.Mvc.ObjectResult!
virtual Microsoft.AspNetCore.Mvc.ControllerBase.Problem(string? detail = null, string? instance = null, int? statusCode = null, string? title = null, string? type = null, System.Collections.Generic.IDictionary<string!, object?>? extensions = null) -> Microsoft.AspNetCore.Mvc.ObjectResult!
virtual Microsoft.AspNetCore.Mvc.ControllerBase.ValidationProblem(string? detail, string? instance, int? statusCode, string? title, string? type, Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary? modelStateDictionary) -> Microsoft.AspNetCore.Mvc.ActionResult!
Expand Down
23 changes: 23 additions & 0 deletions src/Mvc/Mvc.Core/test/ProducesAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,29 @@ public void ProducesAttribute_WithTypeOnly_DoesNotSetContentTypes()
Assert.Empty(producesAttribute.ContentTypes);
}

[Fact]
public void ProducesAttribute_SetsDescription()
{
// Arrange
var producesAttribute = new ProducesAttribute(typeof(Person))
{
Description = "Example"
};

// Act and Assert
Assert.Equal("Example", producesAttribute.Description);
}

[Fact]
public void ProducesAttribute_WithTypeOnly_DoesNotSetDescription()
{
// Arrange
var producesAttribute = new ProducesAttribute(typeof(Person));

// Act and Assert
Assert.Null(producesAttribute.Description);
}

private static ResultExecutedContext CreateResultExecutedContext(ResultExecutingContext context)
{
return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller);
Expand Down
23 changes: 23 additions & 0 deletions src/Mvc/Mvc.Core/test/ProducesResponseTypeAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ public void ProducesResponseTypeAttribute_WithTypeOnly_DoesNotSetContentTypes()
Assert.Null(producesResponseTypeAttribute.ContentTypes);
}

[Fact]
public void ProducesResponseTypeAttribute_SetsDescription()
{
// Arrange
var producesResponseTypeAttribute = new ProducesResponseTypeAttribute(typeof(Person), StatusCodes.Status200OK)
{
Description = "Example"
};

// Act and Assert
Assert.Equal("Example", producesResponseTypeAttribute.Description);
}

[Fact]
public void ProducesResponseTypeAttribute_WithTypeOnly_DoesNotSetDescription()
{
// Arrange
var producesResponseTypeAttribute = new ProducesResponseTypeAttribute(typeof(Person), StatusCodes.Status200OK);

// Act and Assert
Assert.Null(producesResponseTypeAttribute.Description);
}

private class Person
{
public int Id { get; set; }
Expand Down
Loading