From ebdb3c650a04e7b5f1c734ac0c01347dec900b10 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 19 Jan 2018 15:19:12 -0800 Subject: [PATCH] Smooth rough ApiBehavior edges Fixes #7262 --- .../ApiBehaviorApiDescriptionProvider.cs | 116 --------- .../MvcApiExplorerMvcCoreBuilderExtensions.cs | 2 - .../Properties/AssemblyInfo.cs | 3 +- .../ApiBehaviorOptions.cs | 4 +- .../Internal/ApiBehaviorOptionsSetup.cs | 19 +- .../Properties/Resources.Designer.cs | 4 +- .../Resources.resx | 2 +- .../Properties/Resources.Designer.cs | 4 +- .../Resources.resx | 2 +- .../ApiBehaviorApiDescriptionProviderTest.cs | 241 ------------------ .../Binders/BodyModelBinderTests.cs | 4 +- .../ValidationProblemDetailsTest.cs | 6 +- .../ApiBehaviorTest.cs | 31 ++- .../ApiExplorerTest.cs | 90 +------ ...ataContractSerializerInputFormatterTest.cs | 4 +- .../XmlSerializerInputFormatterTests.cs | 4 +- .../MvcServiceCollectionExtensionsTest.cs | 1 - .../VndErrorDescriptionProvider.cs | 13 +- 18 files changed, 54 insertions(+), 496 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs deleted file mode 100644 index 0296189e6b..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiBehaviorApiDescriptionProvider.cs +++ /dev/null @@ -1,116 +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.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/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs index bcf71e8642..0d3b74c378 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs @@ -26,8 +26,6 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); services.TryAddEnumerable( ServiceDescriptor.Transient()); - services.TryAddEnumerable( - ServiceDescriptor.Transient()); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs index 18b9b08433..40008c2f22 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs @@ -10,4 +10,5 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo))] [assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat))] [assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat))] -[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType))] \ No newline at end of file +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType))] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs index 65c64586ce..e955f25768 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs @@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Mvc /// Delegate invoked on actions annotated with to convert invalid /// into an /// - /// By default, the delegate produces a using - /// as the problem format. + /// By default, the delegate produces a that wraps a serialized form + /// of . /// /// public Func InvalidModelStateResponseFactory diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs index 52be8d2e0a..8556f7db56 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Internal @@ -29,16 +30,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal { var errorDetails = _errorDescriptionFactory.CreateErrorDescription( context.ActionDescriptor, - new ValidationProblemDetails(context.ModelState)); + context.ModelState); - return new BadRequestObjectResult(errorDetails) - { - ContentTypes = - { - "application/problem+json", - "application/problem+xml", - }, - }; + var result = (errorDetails is ModelStateDictionary modelState) ? + new BadRequestObjectResult(modelState) : + new BadRequestObjectResult(errorDetails); + + result.ContentTypes.Add("application/problem+json"); + result.ContentTypes.Add("application/problem+json"); + + return result; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 443a6a59b1..2c4d96c4aa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1327,7 +1327,7 @@ namespace Microsoft.AspNetCore.Mvc.Core => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0); /// - /// One or more validation errors occured. + /// One or more validation errors occurred. /// internal static string ValidationProblemDescription_Title { @@ -1335,7 +1335,7 @@ namespace Microsoft.AspNetCore.Mvc.Core } /// - /// One or more validation errors occured. + /// One or more validation errors occurred. /// internal static string FormatValidationProblemDescription_Title() => GetString("ValidationProblemDescription_Title"); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 4a0fd9fd1a..a607e28eff 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -413,7 +413,7 @@ The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. - One or more validation errors occured. + One or more validation errors occurred. Action methods on controllers annotated with {0} must have an attribute route. diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs index 9396dad37e..94864035dd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml => string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0); /// - /// An error occured while deserializing input data. + /// An error occurred while deserializing input data. /// internal static string ErrorDeserializingInputData { @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml } /// - /// An error occured while deserializing input data. + /// An error occurred while deserializing input data. /// internal static string FormatErrorDeserializingInputData() => GetString("ErrorDeserializingInputData"); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx index b3e8d858f3..b83867ae64 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx @@ -121,7 +121,7 @@ The type must be an interface and must be or derive from '{0}'. - An error occured while deserializing input data. + An error occurred while deserializing input data. {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'. diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs deleted file mode 100644 index f5b22d0527..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiBehaviorApiDescriptionProviderTest.cs +++ /dev/null @@ -1,241 +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.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/ModelBinding/Binders/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs index 5cc7342bb3..f1bb2beab9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs @@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var entry = Assert.Single(bindingContext.ModelState); Assert.Equal(string.Empty, entry.Key); var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage; - Assert.Equal("An error occured while deserializing input data.", errorMessage); + Assert.Equal("An error occurred while deserializing input data.", errorMessage); Assert.Null(entry.Value.Errors[0].Exception); } @@ -366,7 +366,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var entry = Assert.Single(bindingContext.ModelState); Assert.Equal(string.Empty, entry.Key); var errorMessage = Assert.Single(entry.Value.Errors).ErrorMessage; - Assert.Equal("An error occured while deserializing input data.", errorMessage); + Assert.Equal("An error occurred while deserializing input data.", errorMessage); Assert.Null(entry.Value.Errors[0].Exception); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs index 1254add386..af4f0d9a34 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc var problemDescription = new ValidationProblemDetails(); // Assert - Assert.Equal("One or more validation errors occured.", problemDescription.Title); + Assert.Equal("One or more validation errors occurred.", problemDescription.Title); Assert.Empty(problemDescription.Errors); } @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc var problemDescription = new ValidationProblemDetails(modelStateDictionary); // Assert - Assert.Equal("One or more validation errors occured.", problemDescription.Title); + Assert.Equal("One or more validation errors occurred.", problemDescription.Title); Assert.Collection( problemDescription.Errors, item => @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc var problemDescription = new ValidationProblemDetails(modelStateDictionary); // Assert - Assert.Equal("One or more validation errors occured.", problemDescription.Title); + Assert.Equal("One or more validation errors occurred.", problemDescription.Title); Assert.Collection( problemDescription.Errors, item => diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index 99703f4cc2..2b83392913 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -1,6 +1,7 @@ // 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 System.Net; using System.Net.Http; @@ -32,14 +33,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests State = "WA", Zip = "Invalid", }; - var expected = new ValidationProblemDetails - { - Errors = - { - ["Zip"] = new[] { @"The field Zip must match the regular expression '\d{5}'." }, - ["Name"] = new[] { "The field Name must be a string with a minimum length of 5 and a maximum length of 30." }, - }, - }; var contactString = JsonConvert.SerializeObject(contactModel); // Act @@ -48,12 +41,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType); - var actual = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - Assert.Equal(expected.Errors.Count, actual.Errors.Count); - foreach (var error in expected.Errors) - { - Assert.Equal(error.Value, actual.Errors[error.Key]); - } + var actual = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + Assert.Collection( + actual.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("Name", kvp.Key); + var error = Assert.Single(kvp.Value); + Assert.Equal("The field Name must be a string with a minimum length of 5 and a maximum length of 30.", error); + }, + kvp => + { + Assert.Equal("Zip", kvp.Key); + var error = Assert.Single(kvp.Value); + Assert.Equal("The field Zip must match the regular expression '\\d{5}'.", error); + } + ); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index d80f301ca1..7b8ae8d175 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1065,85 +1065,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("ApiExplorerInboundOutbound/SuppressedForLinkGeneration", description.RelativePath); } - [Fact] - public async Task ProblemDetails_AddsProblemAsDefaultErrorResult() - { - // Act - var body = await Client.GetStringAsync("ApiExplorerApiController/ActionWithoutParameters"); - var result = JsonConvert.DeserializeObject>(body); - - // Assert - var description = Assert.Single(result); - Assert.Collection( - description.SupportedResponseTypes, - response => - { - Assert.Equal(0, response.StatusCode); - Assert.True(response.IsDefaultResponse); - AssertProblemDetails(response); - }); - } - - [Fact] - public async Task ProblemDetails_AddsProblemAsErrorResultForBadResult_WhenActionHasParameters() - { - // Act - var body = await Client.GetStringAsync("ApiExplorerApiController/ActionWithSomeParameters"); - var result = JsonConvert.DeserializeObject>(body); - - // Assert - var description = Assert.Single(result); - Assert.Collection( - description.SupportedResponseTypes.OrderBy(r => r.StatusCode), - 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); - }); - } - - [Theory] - [InlineData("ApiExplorerApiController/ActionWithIdParameter")] - [InlineData("ApiExplorerApiController/ActionWithIdSuffixParameter")] - public async Task ProblemDetails_AddsProblemAsErrorResultForNotFoundResult_WhenActionHasAnIdParameters(string url) - { - // Act - var body = await Client.GetStringAsync(url); - var result = JsonConvert.DeserializeObject>(body); - - // Assert - var description = Assert.Single(result); - Assert.Collection( - description.SupportedResponseTypes.OrderBy(r => r.StatusCode), - 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); - }); - } - [Fact] public async Task ApiBehavior_AddsMultipartFormDataConsumesConstraint_ForActionsWithFormFileParameters() { @@ -1157,15 +1078,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("multipart/form-data", requestFormat.MediaType); } - private void AssertProblemDetails(ApiExplorerResponseType response) - { - Assert.Equal("Microsoft.AspNetCore.Mvc.ProblemDetails", response.ResponseType); - Assert.Collection( - GetSortedMediaTypes(response), - mediaType => Assert.Equal("application/problem+json", mediaType), - mediaType => Assert.Equal("application/problem+xml", mediaType)); - } - private IEnumerable GetSortedMediaTypes(ApiExplorerResponseType apiResponseType) { return apiResponseType.ResponseFormats @@ -1238,4 +1150,4 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public string FormatterType { get; set; } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs index ab36a45d93..97cac2b253 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var data = await response.Content.ReadAsStringAsync(); - Assert.Contains("An error occured while deserializing input data.", data); + Assert.Contains("An error occurred while deserializing input data.", data); } [Fact] @@ -112,4 +112,4 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs index 0589d349d4..e9e6675cff 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var data = await response.Content.ReadAsStringAsync(); - Assert.Contains("An error occured while deserializing input data.", data); + Assert.Contains("An error occurred while deserializing input data.", data); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index 5fa8242a70..61ce320702 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -447,7 +447,6 @@ namespace Microsoft.AspNetCore.Mvc new Type[] { typeof(DefaultApiDescriptionProvider), - typeof(ApiBehaviorApiDescriptionProvider), typeof(JsonPatchOperationsArrayProvider), } }, diff --git a/test/WebSites/BasicWebSite/VndErrorDescriptionProvider.cs b/test/WebSites/BasicWebSite/VndErrorDescriptionProvider.cs index e63cb90bbd..e74df13b5b 100644 --- a/test/WebSites/BasicWebSite/VndErrorDescriptionProvider.cs +++ b/test/WebSites/BasicWebSite/VndErrorDescriptionProvider.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -20,18 +21,18 @@ namespace BasicWebSite public void OnProvidersExecuting(ErrorDescriptionContext context) { if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute) && - context.Result is ValidationProblemDetails problemDetails) + context.Result is ModelStateDictionary dictionary) { var vndErrors = new List(); - foreach (var item in problemDetails.Errors) + foreach (var item in dictionary) { - foreach (var message in item.Value) + foreach (var modelError in item.Value.Errors) { vndErrors.Add(new VndError { - LogRef = problemDetails.Title, + LogRef = modelError.ErrorMessage, Path = item.Key, - Message = message, + Message = modelError.ErrorMessage, }); } } @@ -40,4 +41,4 @@ namespace BasicWebSite } } } -} \ No newline at end of file +}