From 7ba167fcd843e44211974c4211495438d7398add Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 4 Oct 2017 08:07:04 -0700 Subject: [PATCH] Add 'default response' to API Response Type Also some cleanup and unit tests. --- .../ApiExplorer/ApiResponseType.cs | 10 + .../ApiBehaviorApiDescriptionProvider.cs | 116 +++++++++ .../ApiControllerApiDescriptionProvider.cs | 78 ------ .../MvcApiExplorerMvcCoreBuilderExtensions.cs | 2 +- .../MvcCoreServiceCollectionExtensions.cs | 2 +- ...=> ApiBehaviorApplicationModelProvider.cs} | 4 +- .../ApiBehaviorApiDescriptionProviderTest.cs | 241 ++++++++++++++++++ .../MvcCoreServiceCollectionExtensionsTest.cs | 2 +- ...piBehaviorApplicationModelProviderTest.cs} | 12 +- .../ApiExplorerTest.cs | 8 + .../MvcServiceCollectionExtensionsTest.cs | 4 +- .../ApiExplorerDataFilter.cs | 5 +- 12 files changed, 392 insertions(+), 92 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiControllerApiDescriptionProvider.cs rename src/Microsoft.AspNetCore.Mvc.Core/Internal/{ApiControllerApplicationModelProvider.cs => ApiBehaviorApplicationModelProvider.cs} (93%) create mode 100644 test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/{ApiControllerApplicationModelProviderTest.cs => ApiBehaviorApplicationModelProviderTest.cs} (90%) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs index 4e3307c0b5..6ed9644931 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs @@ -39,5 +39,15 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// Gets or sets the HTTP response status code. /// public int StatusCode { get; set; } + + /// + /// Gets or sets a value indicating whether the response type represents a default response. + /// + /// + /// If an has a default response, then the property should be ignored. This response + /// will be used when a more specific response format does not apply. The common use of a default response is to specify the format + /// for communicating error conditions. + /// + public bool IsDefaultResponse { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs new file mode 100644 index 0000000000..83874b6733 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs @@ -0,0 +1,116 @@ +// 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 System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public class ApiBehaviorApiDescriptionProvider : IApiDescriptionProvider + { + private readonly IModelMetadataProvider _modelMetadaProvider; + + public ApiBehaviorApiDescriptionProvider(IModelMetadataProvider modelMetadataProvider) + { + _modelMetadaProvider = modelMetadataProvider; + } + + /// + /// The order is set to execute after the default provider. + /// + public int Order => -1000 + 10; + + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + + public void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + foreach (var description in context.Results) + { + if (!AppliesTo(description)) + { + continue; + } + + foreach (var responseType in CreateProblemResponseTypes(description)) + { + description.SupportedResponseTypes.Add(responseType); + } + } + } + + public bool AppliesTo(ApiDescription description) + { + return description.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is IApiBehaviorMetadata); + } + + // Check if the parameter is named "id" (e.g. int id) or ends in Id (e.g. personId) + public bool IsIdParameter(ParameterDescriptor parameter) + { + if (parameter.Name == null) + { + return false; + } + + if (string.Equals("id", parameter.Name, StringComparison.Ordinal)) + { + return true; + } + + // We're looking for a name ending with Id, but preceded by a lower case letter. This should match + // the normal PascalCase naming conventions. + if (parameter.Name.Length >= 3 && + parameter.Name.EndsWith("Id", StringComparison.Ordinal) && + char.IsLower(parameter.Name, parameter.Name.Length - 3)) + { + return true; + } + + return false; + } + + public IEnumerable CreateProblemResponseTypes(ApiDescription description) + { + if (description.ActionDescriptor.Parameters.Any() || description.ActionDescriptor.BoundProperties.Any()) + { + // For validation errors. + yield return CreateProblemResponse(StatusCodes.Status400BadRequest); + + if (description.ActionDescriptor.Parameters.Any(p => IsIdParameter(p))) + { + yield return CreateProblemResponse(StatusCodes.Status404NotFound); + } + } + + yield return CreateProblemResponse(statusCode: 0, isDefaultResponse: true); + } + + private ApiResponseType CreateProblemResponse(int statusCode, bool isDefaultResponse = false) + { + return new ApiResponseType + { + ApiResponseFormats = new List + { + new ApiResponseFormat + { + MediaType = "application/problem+json", + }, + new ApiResponseFormat + { + MediaType = "application/problem+xml", + }, + }, + IsDefaultResponse = isDefaultResponse, + ModelMetadata = _modelMetadaProvider.GetMetadataForType(typeof(ProblemDetails)), + StatusCode = statusCode, + Type = typeof(ProblemDetails), + }; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiControllerApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiControllerApiDescriptionProvider.cs deleted file mode 100644 index 4856b19cbd..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiControllerApiDescriptionProvider.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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 System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace Microsoft.AspNetCore.Mvc.ApiExplorer -{ - public class ApiControllerApiDescriptionProvider : IApiDescriptionProvider - { - private readonly IModelMetadataProvider _modelMetadaProvider; - - public ApiControllerApiDescriptionProvider(IModelMetadataProvider modelMetadataProvider) - { - _modelMetadaProvider = modelMetadataProvider; - } - - /// - /// The order is set to execute after the . - /// - public int Order => -1000 + 10; - - public void OnProvidersExecuted(ApiDescriptionProviderContext context) - { - } - - public void OnProvidersExecuting(ApiDescriptionProviderContext context) - { - foreach (var apiDescription in context.Results) - { - if (!apiDescription.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is IApiBehaviorMetadata)) - { - continue; - } - - var parameters = apiDescription.ActionDescriptor.Parameters.Concat(apiDescription.ActionDescriptor.BoundProperties); - if (parameters.Any()) - { - apiDescription.SupportedResponseTypes.Add(CreateProblemResponse(StatusCodes.Status400BadRequest)); - - if (parameters.Any(p => p.Name.EndsWith("id", StringComparison.OrdinalIgnoreCase))) - { - apiDescription.SupportedResponseTypes.Add(CreateProblemResponse(StatusCodes.Status404NotFound)); - } - } - - // We don't have a good way to signal a "default" response type. We'll use 0 to indicate this until we come up - // with something better. - apiDescription.SupportedResponseTypes.Add(CreateProblemResponse(statusCode: 0)); - } - } - - private ApiResponseType CreateProblemResponse(int statusCode) - { - return new ApiResponseType - { - ApiResponseFormats = new List - { - new ApiResponseFormat - { - MediaType = "application/problem+json", - }, - new ApiResponseFormat - { - MediaType = "application/problem+xml", - }, - }, - ModelMetadata = _modelMetadaProvider.GetMetadataForType(typeof(ProblemDetails)), - StatusCode = statusCode, - Type = typeof(ProblemDetails), - }; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs index bb823c3ecb..bcf71e8642 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs @@ -27,7 +27,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddEnumerable( ServiceDescriptor.Transient()); services.TryAddEnumerable( - ServiceDescriptor.Transient()); + ServiceDescriptor.Transient()); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index bc2a5c992d..15549963e6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -160,7 +160,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddEnumerable( ServiceDescriptor.Transient()); services.TryAddEnumerable( - ServiceDescriptor.Transient()); + ServiceDescriptor.Transient()); services.TryAddEnumerable( ServiceDescriptor.Transient()); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiControllerApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs similarity index 93% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiControllerApplicationModelProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index a8ddde6528..968eeef334 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiControllerApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -12,12 +12,12 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Internal { - public class ApiControllerApplicationModelProvider : IApplicationModelProvider + public class ApiBehaviorApplicationModelProvider : IApplicationModelProvider { private readonly ApiBehaviorOptions _apiBehaviorOptions; private readonly ModelStateInvalidFilter _modelStateInvalidFilter; - public ApiControllerApplicationModelProvider(IOptions apiBehaviorOptions, ILoggerFactory loggerFactory) + public ApiBehaviorApplicationModelProvider(IOptions apiBehaviorOptions, ILoggerFactory loggerFactory) { _apiBehaviorOptions = apiBehaviorOptions.Value; if (_apiBehaviorOptions.EnableModelStateInvalidFilter && _apiBehaviorOptions.InvalidModelStateResponseFactory == null) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs new file mode 100644 index 0000000000..f5b22d0527 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs @@ -0,0 +1,241 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public class ApiBehaviorApiDescriptionProviderTest + { + [Fact] + public void AppliesTo_ActionWithoutApiBehavior_ReturnsFalse() + { + // Arrange + var action = new ActionDescriptor() + { + FilterDescriptors = new List(), + }; + var description = new ApiDescription() + { + ActionDescriptor = action, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var result = provider.AppliesTo(description); + + // Assert + Assert.False(result); + } + + [Fact] + public void AppliesTo_ActionWithApiBehavior_ReturnsTrue() + { + // Arrange + var action = new ActionDescriptor() + { + FilterDescriptors = new List() + { + new FilterDescriptor(Mock.Of(), FilterScope.Global), + } + }; + var description = new ApiDescription() + { + ActionDescriptor = action, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var result = provider.AppliesTo(description); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("id")] + [InlineData("personId")] + [InlineData("üId")] + public void IsIdParameter_ParameterNameMatchesConvention_ReturnsTrue(string name) + { + var parameter = new ParameterDescriptor() + { + Name = name, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var result = provider.IsIdParameter(parameter); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("i")] + [InlineData("Id")] + [InlineData("iD")] + [InlineData("persoNId")] + [InlineData("personid")] + [InlineData("ü Id")] + [InlineData("ÜId")] + public void IsIdParameter_ParameterNameDoesNotMatchConvention_ReturnsFalse(string name) + { + var parameter = new ParameterDescriptor() + { + Name = name, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var result = provider.IsIdParameter(parameter); + + // Assert + Assert.False(result); + } + + [Fact] + public void CreateProblemResponseTypes_NoParameters_IncludesDefaultResponse() + { + // Arrange + var action = new ActionDescriptor() + { + FilterDescriptors = new List() + { + new FilterDescriptor(Mock.Of(), FilterScope.Global), + }, + BoundProperties = new List(), + Parameters = new List(), + }; + var description = new ApiDescription() + { + ActionDescriptor = action, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var results = provider.CreateProblemResponseTypes(description); + + // Assert + Assert.Collection( + results.OrderBy(r => r.StatusCode), + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(0, r.StatusCode); + Assert.True(r.IsDefaultResponse); + }); + } + + [Fact] + public void CreateProblemResponseTypes_WithBoundProperty_Includes400Response() + { + // Arrange + var action = new ActionDescriptor() + { + FilterDescriptors = new List() + { + new FilterDescriptor(Mock.Of(), FilterScope.Global), + }, + BoundProperties = new List() + { + new ParameterDescriptor() + }, + Parameters = new List(), + }; + var description = new ApiDescription() + { + ActionDescriptor = action, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var results = provider.CreateProblemResponseTypes(description); + + // Assert + Assert.Collection( + results.OrderBy(r => r.StatusCode), + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(0, r.StatusCode); + Assert.True(r.IsDefaultResponse); + }, + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(400, r.StatusCode); + Assert.False(r.IsDefaultResponse); + }); + } + + [Fact] + public void CreateProblemResponseTypes_WithIdParameter_Includes404Response() + { + // Arrange + var action = new ActionDescriptor() + { + FilterDescriptors = new List() + { + new FilterDescriptor(Mock.Of(), FilterScope.Global), + }, + BoundProperties = new List() + { + }, + Parameters = new List() + { + new ParameterDescriptor() + { + Name = "customerId", + } + }, + }; + var description = new ApiDescription() + { + ActionDescriptor = action, + }; + + var provider = new ApiBehaviorApiDescriptionProvider(new EmptyModelMetadataProvider()); + + // Act + var results = provider.CreateProblemResponseTypes(description); + + // Assert + Assert.Collection( + results.OrderBy(r => r.StatusCode), + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(0, r.StatusCode); + Assert.True(r.IsDefaultResponse); + }, + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(400, r.StatusCode); + Assert.False(r.IsDefaultResponse); + }, + r => + { + Assert.Equal(typeof(ProblemDetails), r.Type); + Assert.Equal(404, r.StatusCode); + Assert.False(r.IsDefaultResponse); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index 5340478ff7..59ddee1789 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Mvc new Type[] { typeof(DefaultApplicationModelProvider), - typeof(ApiControllerApplicationModelProvider), + typeof(ApiBehaviorApplicationModelProvider), } }, }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiControllerApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs similarity index 90% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiControllerApplicationModelProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index 15a00e5445..0f10c182c6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiControllerApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -11,7 +11,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal { - public class ApiControllerApplicationModelProviderTest + public class ApiBehaviorApplicationModelProviderTest { [Fact] public void OnProvidersExecuting_AddsModelStateInvalidFilter_IfTypeIsAnnotatedWithAttribute() @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal InvalidModelStateResponseFactory = _ => null, }); - var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); + var provider = new ApiBehaviorApplicationModelProvider(options, NullLoggerFactory.Instance); // Act provider.OnProvidersExecuting(context); @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal EnableModelStateInvalidFilter = false, }); - var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); + var provider = new ApiBehaviorApplicationModelProvider(options, NullLoggerFactory.Instance); // Act provider.OnProvidersExecuting(context); @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal InvalidModelStateResponseFactory = _ => null, }); - var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); + var provider = new ApiBehaviorApplicationModelProvider(options, NullLoggerFactory.Instance); // Act provider.OnProvidersExecuting(context); @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal EnableModelStateInvalidFilter = false, }); - var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); + var provider = new ApiBehaviorApplicationModelProvider(options, NullLoggerFactory.Instance); // Act provider.OnProvidersExecuting(context); @@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal InvalidModelStateResponseFactory = _ => null, }); - var provider = new ApiControllerApplicationModelProvider(options, NullLoggerFactory.Instance); + var provider = new ApiBehaviorApplicationModelProvider(options, NullLoggerFactory.Instance); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 3d969234b6..c7a3f034a0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1079,6 +1079,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests response => { Assert.Equal(0, response.StatusCode); + Assert.True(response.IsDefaultResponse); AssertProblemDetails(response); }); } @@ -1097,12 +1098,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests response => { Assert.Equal(0, response.StatusCode); + Assert.True(response.IsDefaultResponse); AssertProblemDetails(response); }, response => Assert.Equal(200, response.StatusCode), response => { Assert.Equal(400, response.StatusCode); + Assert.False(response.IsDefaultResponse); AssertProblemDetails(response); }); } @@ -1123,17 +1126,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests response => { Assert.Equal(0, response.StatusCode); + Assert.True(response.IsDefaultResponse); AssertProblemDetails(response); }, response => Assert.Equal(200, response.StatusCode), response => { Assert.Equal(400, response.StatusCode); + Assert.False(response.IsDefaultResponse); AssertProblemDetails(response); }, response => { Assert.Equal(404, response.StatusCode); + Assert.False(response.IsDefaultResponse); AssertProblemDetails(response); }); } @@ -1199,6 +1205,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public string ResponseType { get; set; } public int StatusCode { get; set; } + + public bool IsDefaultResponse { get; set; } } private class ApiExplorerResponseFormat diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index c71cb03802..87a3a1b95a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -417,7 +417,7 @@ namespace Microsoft.AspNetCore.Mvc typeof(CorsApplicationModelProvider), typeof(AuthorizationApplicationModelProvider), typeof(TempDataApplicationModelProvider), - typeof(ApiControllerApplicationModelProvider), + typeof(ApiBehaviorApplicationModelProvider), } }, { @@ -425,7 +425,7 @@ namespace Microsoft.AspNetCore.Mvc new Type[] { typeof(DefaultApiDescriptionProvider), - typeof(ApiControllerApiDescriptionProvider), + typeof(ApiBehaviorApiDescriptionProvider), typeof(JsonPatchOperationsArrayProvider), } }, diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs index a028cf3a0d..dbe476a3dd 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs @@ -89,7 +89,8 @@ namespace ApiExplorerWebSite var responseType = new ApiExplorerResponseType() { StatusCode = response.StatusCode, - ResponseType = response.Type?.FullName + ResponseType = response.Type?.FullName, + IsDefaultResponse = response.IsDefaultResponse, }; foreach(var responseFormat in response.ApiResponseFormats) @@ -152,6 +153,8 @@ namespace ApiExplorerWebSite public string ResponseType { get; set; } public int StatusCode { get; set; } + + public bool IsDefaultResponse { get; set; } } private class ApiExplorerResponseFormat