parent
2c6ba372c1
commit
ebdb3c650a
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The order is set to execute after the default provider.
|
||||
/// </remarks>
|
||||
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<ApiResponseType> 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<ApiResponseFormat>
|
||||
{
|
||||
new ApiResponseFormat
|
||||
{
|
||||
MediaType = "application/problem+json",
|
||||
},
|
||||
new ApiResponseFormat
|
||||
{
|
||||
MediaType = "application/problem+xml",
|
||||
},
|
||||
},
|
||||
IsDefaultResponse = isDefaultResponse,
|
||||
ModelMetadata = _modelMetadaProvider.GetMetadataForType(typeof(ProblemDetails)),
|
||||
StatusCode = statusCode,
|
||||
Type = typeof(ProblemDetails),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +26,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IApiDescriptionProvider, ApiBehaviorApiDescriptionProvider>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType))]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Delegate invoked on actions annotated with <see cref="ApiControllerAttribute"/> to convert invalid
|
||||
/// <see cref="ModelStateDictionary"/> into an <see cref="IActionResult"/>
|
||||
/// <para>
|
||||
/// By default, the delegate produces a <see cref="BadRequestObjectResult"/> using <see cref="ProblemDetails"/>
|
||||
/// as the problem format.
|
||||
/// By default, the delegate produces a <see cref="BadRequestObjectResult"/> that wraps a serialized form
|
||||
/// of <see cref="ModelStateDictionary"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Func<ActionContext, IActionResult> InvalidModelStateResponseFactory
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1327,7 +1327,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// One or more validation errors occured.
|
||||
/// One or more validation errors occurred.
|
||||
/// </summary>
|
||||
internal static string ValidationProblemDescription_Title
|
||||
{
|
||||
|
|
@ -1335,7 +1335,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// One or more validation errors occured.
|
||||
/// One or more validation errors occurred.
|
||||
/// </summary>
|
||||
internal static string FormatValidationProblemDescription_Title()
|
||||
=> GetString("ValidationProblemDescription_Title");
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@
|
|||
<value>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.</value>
|
||||
</data>
|
||||
<data name="ValidationProblemDescription_Title" xml:space="preserve">
|
||||
<value>One or more validation errors occured.</value>
|
||||
<value>One or more validation errors occurred.</value>
|
||||
</data>
|
||||
<data name="ApiController_AttributeRouteRequired" xml:space="preserve">
|
||||
<value>Action methods on controllers annotated with {0} must have an attribute route.</value>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
=> string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// An error occured while deserializing input data.
|
||||
/// An error occurred while deserializing input data.
|
||||
/// </summary>
|
||||
internal static string ErrorDeserializingInputData
|
||||
{
|
||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occured while deserializing input data.
|
||||
/// An error occurred while deserializing input data.
|
||||
/// </summary>
|
||||
internal static string FormatErrorDeserializingInputData()
|
||||
=> GetString("ErrorDeserializingInputData");
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@
|
|||
<value>The type must be an interface and must be or derive from '{0}'.</value>
|
||||
</data>
|
||||
<data name="ErrorDeserializingInputData" xml:space="preserve">
|
||||
<value>An error occured while deserializing input data.</value>
|
||||
<value>An error occurred while deserializing input data.</value>
|
||||
</data>
|
||||
<data name="RequiredProperty_MustHaveDataMemberRequired" xml:space="preserve">
|
||||
<value>{0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.</value>
|
||||
|
|
|
|||
|
|
@ -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<FilterDescriptor>(),
|
||||
};
|
||||
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<FilterDescriptor>()
|
||||
{
|
||||
new FilterDescriptor(Mock.Of<IApiBehaviorMetadata>(), 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<FilterDescriptor>()
|
||||
{
|
||||
new FilterDescriptor(Mock.Of<IApiBehaviorMetadata>(), FilterScope.Global),
|
||||
},
|
||||
BoundProperties = new List<ParameterDescriptor>(),
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
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<FilterDescriptor>()
|
||||
{
|
||||
new FilterDescriptor(Mock.Of<IApiBehaviorMetadata>(), FilterScope.Global),
|
||||
},
|
||||
BoundProperties = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
},
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
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<FilterDescriptor>()
|
||||
{
|
||||
new FilterDescriptor(Mock.Of<IApiBehaviorMetadata>(), FilterScope.Global),
|
||||
},
|
||||
BoundProperties = new List<ParameterDescriptor>()
|
||||
{
|
||||
},
|
||||
Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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<ValidationProblemDetails>(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<Dictionary<string, string[]>>(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]
|
||||
|
|
|
|||
|
|
@ -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<List<ApiExplorerData>>(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<List<ApiExplorerData>>(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<List<ApiExplorerData>>(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<string> GetSortedMediaTypes(ApiExplorerResponseType apiResponseType)
|
||||
{
|
||||
return apiResponseType.ResponseFormats
|
||||
|
|
@ -1238,4 +1150,4 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public string FormatterType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,7 +447,6 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
new Type[]
|
||||
{
|
||||
typeof(DefaultApiDescriptionProvider),
|
||||
typeof(ApiBehaviorApiDescriptionProvider),
|
||||
typeof(JsonPatchOperationsArrayProvider),
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<VndError>();
|
||||
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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue