From d2bb674b0a665253d64696cdce26284b4bd930eb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 6 Jul 2018 16:44:07 -0700 Subject: [PATCH] Add support for default response (#8028) * Add support for default response Fixes #6828 --- .../ApiResponseTypeProvider.cs | 75 +++++++------ .../ApiConventionTypeAttribute.cs | 3 +- .../IApiDefaultResponseMetadataProvider.cs | 12 +++ .../DefaultApiConventions.cs | 4 + .../ProducesDefaultResponseTypeAttribute.cs | 49 +++++++++ .../ApiResponseTypeProviderTest.cs | 102 ++++++++++++++++++ .../ApiConventionTypeAttributeTest.cs | 35 +++--- .../ApiExplorer/ApiConventionResultTest.cs | 4 + .../ApiExplorerTest.cs | 18 +++- 9 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index d6c7cd7661..ec791aaee0 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer _mvcOptions = mvcOptions; } - public IList GetApiResponseTypes(ControllerActionDescriptor action) + public ICollection GetApiResponseTypes(ControllerActionDescriptor action) { // We only provide response info if we can figure out a type that is a user-data type. // Void /Task object/IActionResult will result in no data. @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); var responseMetadataAttributes = GetResponseMetadataAttributes(action); - if (responseMetadataAttributes.Count == 0 && + if (responseMetadataAttributes.Count == 0 && action.Properties.TryGetValue(typeof(ApiConventionResult), out var result)) { // Action does not have any conventions. Use conventions on it if present. @@ -67,14 +67,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer .ToArray(); } - private IList GetApiResponseTypes( + private ICollection GetApiResponseTypes( IReadOnlyList responseMetadataAttributes, Type type) { - var results = new List(); - - // Build list of all possible return types (and status codes) for an action. - var objectTypes = new Dictionary(); + var results = new Dictionary(); // Get the content type that the action explicitly set to support. // Walk through all 'filter' attributes in order, and allow each one to see or override @@ -86,7 +83,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { metadataAttribute.SetContentTypes(contentTypes); - if (metadataAttribute.Type == typeof(void) && + ApiResponseType apiResponseType; + + if (metadataAttribute is IApiDefaultResponseMetadataProvider) + { + apiResponseType = new ApiResponseType + { + IsDefaultResponse = true, + Type = metadataAttribute.Type, + }; + } + else if (metadataAttribute.Type == typeof(void) && type != null && (metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created)) { @@ -94,20 +101,38 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer // In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a // [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred // from the return type. - objectTypes[metadataAttribute.StatusCode] = type; + apiResponseType = new ApiResponseType + { + StatusCode = metadataAttribute.StatusCode, + Type = type, + }; } else if (metadataAttribute.Type != null) { - objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; + apiResponseType = new ApiResponseType + { + StatusCode = metadataAttribute.StatusCode, + Type = metadataAttribute.Type, + }; } + else + { + continue; + } + + results[apiResponseType.StatusCode] = apiResponseType; } } // Set the default status only when no status has already been set explicitly - if (objectTypes.Count == 0 && type != null) + if (results.Count == 0 && type != null) { - objectTypes[StatusCodes.Status200OK] = type; + results[StatusCodes.Status200OK] = new ApiResponseType + { + StatusCode = StatusCodes.Status200OK, + Type = type, + }; } if (contentTypes.Count == 0) @@ -117,25 +142,15 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); - foreach (var objectType in objectTypes) + foreach (var apiResponse in results.Values) { - if (objectType.Value == null || objectType.Value == typeof(void)) + var responseType = apiResponse.Type; + if (responseType == null || responseType == typeof(void)) { - results.Add(new ApiResponseType() - { - StatusCode = objectType.Key, - Type = objectType.Value - }); - continue; } - var apiResponseType = new ApiResponseType() - { - Type = objectType.Value, - StatusCode = objectType.Key, - ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value) - }; + apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType); foreach (var contentType in contentTypes) { @@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( contentType, - objectType.Value); + responseType); if (formatterSupportedContentTypes == null) { @@ -152,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { - apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() + apiResponse.ApiResponseFormats.Add(new ApiResponseFormat { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, @@ -160,11 +175,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } } } - - results.Add(apiResponseType); } - return results; + return results.Values; } private Type GetDeclaredReturnType(ControllerActionDescriptor action) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs index 9c44452961..e8c10cd3ac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention( methodDisplayName, Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine, - $"{nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"); + $"{nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"); throw new ArgumentException(errorMessage, nameof(conventionType)); } @@ -83,6 +83,7 @@ namespace Microsoft.AspNetCore.Mvc private static bool IsAllowedAttribute(object attribute) { return attribute is ProducesResponseTypeAttribute || + attribute is ProducesDefaultResponseTypeAttribute || attribute is ApiConventionNameMatchAttribute; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs new file mode 100644 index 0000000000..a841fc9f0a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides a return type for all HTTP status codes that are not covered by other instances. + /// + public interface IApiDefaultResponseMetadataProvider : IApiResponseMetadataProvider + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs index 46b3ec6e80..3508fb98cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Mvc { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Get( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] @@ -18,6 +19,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Post( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] @@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Put( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] @@ -40,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Delete( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs new file mode 100644 index 0000000000..3022327a5b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that specifies the for all HTTP status codes that are not covered by . + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ProducesDefaultResponseTypeAttribute : Attribute, IApiDefaultResponseMetadataProvider + { + /// + /// Initializes an instance of . + /// + public ProducesDefaultResponseTypeAttribute() + : this(typeof(void)) + { + } + + /// + /// Initializes an instance of . + /// + /// The of object that is going to be written in the response. + public ProducesDefaultResponseTypeAttribute(Type type) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + } + + /// + /// Gets or sets the type of the value returned by an action. + /// + public Type Type { get; } + + /// + /// Gets or sets the HTTP status code of the response. + /// + public int StatusCode { get; } + + /// + void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes) + { + // Users are supposed to use the 'Produces' attribute to set the content types that an action can support. + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 7f3247deac..29543d26f1 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -72,6 +72,60 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public Task> Get(int id) => null; } + [Fact] + public void GetApiResponseTypes_CombinesFilters() + { + // Arrange + var filterDescriptors = new[] + { + new FilterDescriptor(new ProducesResponseTypeAttribute(400), FilterScope.Global), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(object), 201), FilterScope.Controller), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(ProblemDetails), 400), FilterScope.Controller), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(BaseModel), 201), FilterScope.Action), + new FilterDescriptor(new ProducesResponseTypeAttribute(404), FilterScope.Action), + }; + + var actionDescriptor = new ControllerActionDescriptor + { + FilterDescriptors = filterDescriptors, + MethodInfo = typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController).GetMethod(nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)), + }; + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(typeof(ProblemDetails), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + [Fact] public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem() { @@ -159,6 +213,54 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public Task> PostModel(int id, BaseModel model) => null; } + [Fact] + public void GetApiResponseTypes_ReturnsDefaultProblemResponse() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(201), + new ProducesResponseTypeAttribute(404), + new ProducesDefaultResponseTypeAttribute(typeof(SerializableError)), + }); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.True(responseType.IsDefaultResponse); + Assert.Equal(typeof(SerializableError), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs index edc7ce7747..d6ff2a28f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Testing; @@ -15,11 +16,10 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute() { // Arrange - var expected = $"Method {typeof(ConventionWithProducesAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - typeof(ProducesAttribute).FullName + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + + var expected = GetErrorMessage(methodName, attribute); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -38,11 +38,9 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfConventionMethodHasRouteAttribute() { // Arrange - var expected = $"Method {typeof(ConventionWithRouteAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - typeof(HttpGetAttribute).FullName + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWithRouteAttribute).FullName + '.' + nameof(ConventionWithRouteAttribute.Get); + var attribute = typeof(HttpGetAttribute); + var expected = GetErrorMessage(methodName, attribute); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -61,11 +59,9 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention() { // Arrange - var expected = $"Method {typeof(ConventionWitUnsupportedAttributes).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - string.Join(Environment.NewLine, typeof(ProducesAttribute).FullName, typeof(ServiceFilterAttribute).FullName, typeof(AuthorizeAttribute).FullName) + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWitUnsupportedAttributes).FullName + '.' + nameof(ConventionWitUnsupportedAttributes.Get); + var attributes = new[] { typeof(ProducesAttribute), typeof(ServiceFilterAttribute), typeof(AuthorizeAttribute) }; + var expected = GetErrorMessage(methodName, attributes); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -82,5 +78,14 @@ namespace Microsoft.AspNetCore.Mvc [Authorize] public static void Get() { } } + + private static string GetErrorMessage(string methodName, params Type[] attributes) + { + return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs index 69079bd8b0..e1290a0421 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs @@ -111,6 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(200, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); } @@ -130,6 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(201, r.StatusCode), r => Assert.Equal(400, r.StatusCode)); } @@ -152,6 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(204, r.StatusCode), r => Assert.Equal(400, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); @@ -175,6 +178,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(200, r.StatusCode), r => Assert.Equal(400, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index e575967651..9b556a42a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -711,7 +711,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); - Assert.Equal(2, description.SupportedResponseTypes.Count); Assert.Collection( description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), @@ -749,7 +748,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); - Assert.Equal(2, description.SupportedResponseTypes.Count); Assert.Collection( description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), @@ -1171,6 +1169,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(Product).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode); @@ -1255,6 +1257,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(201, responseType.StatusCode); @@ -1283,6 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(204, responseType.StatusCode); @@ -1316,6 +1326,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode);