Use ClientErrorData to configure ClientErrorResultFilter

Fixes #8289
This commit is contained in:
Pranav K 2018-08-23 10:37:58 -07:00
parent 17d72c2b94
commit 667ad4daff
16 changed files with 265 additions and 124 deletions

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class ApiBehaviorOptions : IEnumerable<ICompatibilitySwitch>
{
private readonly CompatibilitySwitch<bool> _suppressUseClientErrorFactory;
private readonly CompatibilitySwitch<bool> _suppressMapClientErrors;
private readonly CompatibilitySwitch<bool> _suppressUseValidationProblemDetailsForInvalidModelStateResponses;
private readonly ICompatibilitySwitch[] _switches;
@ -26,11 +26,11 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public ApiBehaviorOptions()
{
_suppressUseClientErrorFactory = new CompatibilitySwitch<bool>(nameof(SuppressUseClientErrorFactory));
_suppressMapClientErrors = new CompatibilitySwitch<bool>(nameof(SuppressMapClientErrors));
_suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch<bool>(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses));
_switches = new[]
{
_suppressUseClientErrorFactory,
_suppressMapClientErrors,
_suppressUseValidationProblemDetailsForInvalidModelStateResponses,
};
}
@ -71,12 +71,16 @@ namespace Microsoft.AspNetCore.Mvc
public bool SuppressConsumesConstraintForFormFileParameters { get; set; }
/// <summary>
/// Gets or sets a value that determines if controllers with <see cref="ApiControllerAttribute"/> use <see cref="ClientErrorFactory"/>
/// to transform certain certain client errors.
/// Gets or sets a value that determines if controllers with <see cref="ApiControllerAttribute"/>
/// transform certain certain client errors.
/// <para>
/// When <c>false</c>, <see cref="ClientErrorFactory"/> is used to transform <see cref="IClientErrorActionResult"/> to the value
/// specified by the factory. In the default case, this converts <see cref="StatusCodeResult"/> instances to an <see cref="ObjectResult"/>
/// with <see cref="ProblemDetails"/>.
/// When <c>false</c>, a result filter is added to API controller actions that transforms <see cref="IClientErrorActionResult"/>.
/// By default, <see cref="ClientErrorMapping"/> is used to map <see cref="IClientErrorActionResult"/> to a
/// <see cref="ProblemDetails"/> instance (returned as the value for <see cref="ObjectResult"/>).
/// </para>
/// <para>
/// To customize the output of the filter (for e.g. to return a different error type), register a custom
/// implementation of of <see cref="IClientErrorFactory"/> in the service collection.
/// </para>
/// </summary>
/// <value>
@ -102,11 +106,11 @@ namespace Microsoft.AspNetCore.Mvc
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// </remarks>
public bool SuppressUseClientErrorFactory
public bool SuppressMapClientErrors
{
// Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property.
get => _suppressUseClientErrorFactory.Value;
set => _suppressUseClientErrorFactory.Value = value;
get => _suppressMapClientErrors.Value;
set => _suppressMapClientErrors.Value = value;
}
/// <summary>
@ -148,11 +152,15 @@ namespace Microsoft.AspNetCore.Mvc
}
/// <summary>
/// Gets a map of HTTP status codes to <see cref="IActionResult"/> factories.
/// Configured factories are used when <see cref="SuppressUseClientErrorFactory"/> is <see langword="false"/>.
/// Gets a map of HTTP status codes to <see cref="ClientErrorData"/>. Configured values
/// are used to transform <see cref="IClientErrorActionResult"/> to an <see cref="ObjectResult"/>
/// instance where the <see cref="ObjectResult.Value"/> is <see cref="ProblemDetails"/>.
/// <para>
/// Use of this feature can be disabled by resetting <see cref="SuppressMapClientErrors"/>.
/// </para>
/// </summary>
public IDictionary<int, Func<ActionContext, IActionResult>> ClientErrorFactory { get; } =
new Dictionary<int, Func<ActionContext, IActionResult>>();
public IDictionary<int, ClientErrorData> ClientErrorMapping { get; } =
new Dictionary<int, ClientErrorData>();
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{

View File

@ -0,0 +1,29 @@
// 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
{
/// <summary>
/// Information for producing client errors. This type is used to configure client errors
/// produced by consumers of <see cref="ApiBehaviorOptions.ClientErrorMapping"/>.
/// </summary>
public class ClientErrorData
{
/// <summary>
/// Gets or sets a link (URI) that describes the client error.
/// </summary>
/// <remarks>
/// By default, this maps to <see cref="ProblemDetails.Type"/>.
/// </remarks>
public string Link { get; set; }
/// <summary>
/// Gets or sets the summary of the client error.
/// </summary>
/// <remarks>
/// By default, this maps to <see cref="ProblemDetails.Title"/> and should not change
/// between multiple occurences of the same error.
/// </remarks>
public string Title { get; set; }
}
}

View File

@ -257,6 +257,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IActionResultExecutor<RedirectToRouteResult>, RedirectToRouteResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<ContentResult>, ContentResultExecutor>();
services.TryAddSingleton<IClientErrorFactory, ProblemDetailsClientErrorFactory>();
//
// Route Handlers

View File

@ -2,7 +2,6 @@
// 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 Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Logging;
@ -11,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
{
private readonly IDictionary<int, Func<ActionContext, IActionResult>> _clientErrorFactory;
private readonly IClientErrorFactory _clientErrorFactory;
private readonly ILogger<ClientErrorResultFilter> _logger;
/// <summary>
@ -20,10 +19,10 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
public int Order => -2000;
public ClientErrorResultFilter(
ApiBehaviorOptions apiBehaviorOptions,
IClientErrorFactory clientErrorFactory,
ILogger<ClientErrorResultFilter> logger)
{
_clientErrorFactory = apiBehaviorOptions?.ClientErrorFactory ?? throw new ArgumentNullException(nameof(apiBehaviorOptions));
_clientErrorFactory = clientErrorFactory ?? throw new ArgumentNullException(nameof(clientErrorFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@ -38,16 +37,19 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
throw new ArgumentNullException(nameof(context));
}
if (context.Result is IClientErrorActionResult clientErrorActionResult &&
clientErrorActionResult.StatusCode is int statusCode &&
_clientErrorFactory.TryGetValue(statusCode, out var factory))
if (!(context.Result is IClientErrorActionResult clientError))
{
var result = factory(context);
_logger.TransformingClientError(context.Result.GetType(), result?.GetType(), statusCode);
context.Result = factory(context);
return;
}
var result = _clientErrorFactory.GetClientError(context, clientError);
if (result == null)
{
return;
}
_logger.TransformingClientError(context.Result.GetType(), result?.GetType(), clientError.StatusCode);
context.Result = result;
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Infrastructure
{
/// <summary>
/// A factory for producing client errors. This contract is used by controllers annotated
/// with <see cref="ApiControllerAttribute"/> to transform <see cref="IClientErrorActionResult"/>.
/// </summary>
public interface IClientErrorFactory
{
/// <summary>
/// Transforms <paramref name="clientError"/> for the specified <paramref name="actionContext"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="clientError">The <see cref="IClientErrorActionResult"/>.</param>
/// <returns>THe <see cref="IActionResult"/> that would be returned to the client.</returns>
IActionResult GetClientError(ActionContext actionContext, IClientErrorActionResult clientError);
}
}

View File

@ -0,0 +1,44 @@
// 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.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
internal class ProblemDetailsClientErrorFactory : IClientErrorFactory
{
private readonly ApiBehaviorOptions _options;
public ProblemDetailsClientErrorFactory(IOptions<ApiBehaviorOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public IActionResult GetClientError(ActionContext actionContext, IClientErrorActionResult clientError)
{
var problemDetails = new ProblemDetails
{
Status = clientError.StatusCode,
Type = "about:blank",
};
if (clientError.StatusCode is int statusCode &&
_options.ClientErrorMapping.TryGetValue(statusCode, out var errorData))
{
problemDetails.Title = errorData.Title;
problemDetails.Type = errorData.Link;
}
return new ObjectResult(problemDetails)
{
StatusCode = problemDetails.Status,
ContentTypes =
{
"application/problem+json",
"application/problem+xml",
},
};
}
}
}

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public ApiBehaviorApplicationModelProvider(
IOptions<ApiBehaviorOptions> apiBehaviorOptions,
IModelMetadataProvider modelMetadataProvider,
IClientErrorFactory clientErrorFactory,
ILoggerFactory loggerFactory)
{
_apiBehaviorOptions = apiBehaviorOptions.Value;
@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
loggerFactory.CreateLogger<ModelStateInvalidFilter>());
_clientErrorResultFilter = new ClientErrorResultFilter(
_apiBehaviorOptions,
clientErrorFactory,
loggerFactory.CreateLogger<ClientErrorResultFilter>());
}
@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private void AddClientErrorFilter(ActionModel actionModel)
{
if (_apiBehaviorOptions.SuppressUseClientErrorFactory)
if (_apiBehaviorOptions.SuppressMapClientErrors)
{
return;
}

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (Version < CompatibilityVersion.Version_2_2)
{
dictionary[nameof(ApiBehaviorOptions.SuppressUseClientErrorFactory)] = true;
dictionary[nameof(ApiBehaviorOptions.SuppressMapClientErrors)] = true;
dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true;
}
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
options.InvalidModelStateResponseFactory = DefaultFactory;
ConfigureClientErrorFactories(options);
ConfigureClientErrorMapping(options);
}
public override void PostConfigure(string name, ApiBehaviorOptions options)
@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
base.PostConfigure(name, options);
// We want to use problem details factory only if
// (a) it has not been opted out of (SuppressUseClientErrorFactory = true)
// (a) it has not been opted out of (SuppressMapClientErrors = true)
// (b) a different factory was configured
if (!options.SuppressUseClientErrorFactory &&
if (!options.SuppressMapClientErrors &&
object.ReferenceEquals(options.InvalidModelStateResponseFactory, DefaultFactory))
{
options.InvalidModelStateResponseFactory = ProblemDetailsFactory;
@ -67,77 +67,55 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
// Internal for unit testing
internal static void ConfigureClientErrorFactories(ApiBehaviorOptions options)
internal static void ConfigureClientErrorMapping(ApiBehaviorOptions options)
{
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[400] = new ClientErrorData
{
Status = 400,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
Title = Resources.ApiConventions_Title_400,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[401] = new ClientErrorData
{
Status = 401,
Type = "https://tools.ietf.org/html/rfc7235#section-3.1",
Link = "https://tools.ietf.org/html/rfc7235#section-3.1",
Title = Resources.ApiConventions_Title_401,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[403] = new ClientErrorData
{
Status = 403,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.3",
Title = Resources.ApiConventions_Title_403,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[404] = new ClientErrorData
{
Status = 404,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = Resources.ApiConventions_Title_404,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[406] = new ClientErrorData
{
Status = 406,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.6",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.6",
Title = Resources.ApiConventions_Title_406,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[409] = new ClientErrorData
{
Status = 409,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
Title = Resources.ApiConventions_Title_409,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[415] = new ClientErrorData
{
Status = 415,
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.13",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.13",
Title = Resources.ApiConventions_Title_415,
});
};
AddClientErrorFactory(new ProblemDetails
options.ClientErrorMapping[422] = new ClientErrorData
{
Status = 422,
Type = "https://tools.ietf.org/html/rfc4918#section-11.2",
Link = "https://tools.ietf.org/html/rfc4918#section-11.2",
Title = Resources.ApiConventions_Title_422,
});
void AddClientErrorFactory(ProblemDetails problemDetails)
{
var statusCode = problemDetails.Status.Value;
options.ClientErrorFactory[statusCode] = _ => new ObjectResult(problemDetails)
{
StatusCode = statusCode,
ContentTypes =
{
"application/problem+json",
"application/problem+xml",
},
};
}
};
}
private static IActionResult DefaultInvalidModelStateResponse(ActionContext context)

View File

@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static readonly Action<ILogger, Type, Type, Type, Exception> _notMostEffectiveFilter;
private static readonly Action<ILogger, IEnumerable<IOutputFormatter>, Exception> _registeredOutputFormatters;
private static readonly Action<ILogger, Type, Type, int, Exception> _transformingClientError;
private static readonly Action<ILogger, Type, int?, Type, Exception> _transformingClientError;
static MvcCoreLoggerExtensions()
{
@ -651,10 +651,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
48,
"Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request.");
_transformingClientError = LoggerMessage.Define<Type, Type, int>(
_transformingClientError = LoggerMessage.Define<Type, int?, Type>(
LogLevel.Trace,
new EventId(49, nameof(Infrastructure.ClientErrorResultFilter)),
"Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType} produced from ClientErrorFactory'.");
"Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType}.");
}
public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable<IOutputFormatter> outputFormatters)
@ -1585,9 +1585,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int statusCode)
public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int? statusCode)
{
_transformingClientError(logger, initialType, replacedType, statusCode, null);
_transformingClientError(logger, initialType, statusCode, replacedType, null);
}
private static void LogFilterExecutionPlan(

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc
public string Type { get; set; }
/// <summary>
/// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence
/// A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
/// see[RFC7231], Section 3.4).
/// </summary>
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc
public string Detail { get; set; }
/// <summary>
/// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
/// A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.
/// </summary>
public string Instance { get; set; }
}

View File

@ -32,35 +32,25 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
}
[Fact]
public void OnResultExecuting_DoesNothing_IfStatusCodeDoesNotExistInApiBehaviorOptions()
public void OnResultExecuting_DoesNothing_IfTransformedValueIsNull()
{
// Arrange
var actionResult = new NotFoundResult();
var context = GetContext(actionResult);
var filter = GetFilter(new ApiBehaviorOptions());
// Act
filter.OnResultExecuting(context);
// Assert
Assert.Same(actionResult, context.Result);
}
[Fact]
public void OnResultExecuting_DoesNothing_IfResultDoesNotHaveStatusCode()
{
// Arrange
var actionResult = new Mock<IActionResult>()
.As<IClientErrorActionResult>()
.Object;
var context = GetContext(actionResult);
var filter = GetFilter(new ApiBehaviorOptions());
var factory = new Mock<IClientErrorFactory>();
factory
.Setup(f => f.GetClientError(It.IsAny<ActionContext>(), It.IsAny<IClientErrorActionResult>()))
.Returns((IActionResult)null)
.Verifiable();
var filter = new ClientErrorResultFilter(factory.Object, NullLogger<ClientErrorResultFilter>.Instance);
// Act
filter.OnResultExecuting(context);
// Assert
Assert.Same(actionResult, context.Result);
factory.Verify();
}
[Fact]
@ -78,18 +68,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
Assert.Same(Result, context.Result);
}
private static ClientErrorResultFilter GetFilter(ApiBehaviorOptions options = null)
private static ClientErrorResultFilter GetFilter()
{
var apiBehaviorOptions = options ?? GetOptions();
var filter = new ClientErrorResultFilter(apiBehaviorOptions, NullLogger<ClientErrorResultFilter>.Instance);
return filter;
}
var factory = Mock.Of<IClientErrorFactory>(
f => f.GetClientError(It.IsAny<ActionContext>(), It.IsAny<IClientErrorActionResult>()) == Result);
private static ApiBehaviorOptions GetOptions()
{
var apiBehaviorOptions = new ApiBehaviorOptions();
apiBehaviorOptions.ClientErrorFactory[404] = _ => Result;
return apiBehaviorOptions;
return new ClientErrorResultFilter(factory, NullLogger<ClientErrorResultFilter>.Instance);
}
private static ResultExecutingContext GetContext(IActionResult actionResult)

View File

@ -0,0 +1,65 @@
// 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 Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class ProblemDetalsClientErrorFactoryTest
{
[Fact]
public void GetClientError_ReturnsProblemDetails_IfNoMappingWasFound()
{
// Arrange
var clientError = new UnsupportedMediaTypeResult();
var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions
{
ClientErrorMapping =
{
[405] = new ClientErrorData { Link = "Some link", Title = "Summary" },
},
}));
// Act
var result = factory.GetClientError(new ActionContext(), clientError);
// Assert
var objectResult = Assert.IsType<ObjectResult>(result);
Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes);
var problemDetails = Assert.IsType<ProblemDetails>(objectResult.Value);
Assert.Equal(415, problemDetails.Status);
Assert.Equal("about:blank", problemDetails.Type);
Assert.Null(problemDetails.Title);
Assert.Null(problemDetails.Detail);
Assert.Null(problemDetails.Instance);
}
[Fact]
public void GetClientError_ReturnsProblemDetails()
{
// Arrange
var clientError = new UnsupportedMediaTypeResult();
var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions
{
ClientErrorMapping =
{
[415] = new ClientErrorData { Link = "Some link", Title = "Summary" },
},
}));
// Act
var result = factory.GetClientError(new ActionContext(), clientError);
// Assert
var objectResult = Assert.IsType<ObjectResult>(result);
Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes);
var problemDetails = Assert.IsType<ProblemDetails>(objectResult.Value);
Assert.Equal(415, problemDetails.Status);
Assert.Equal("Some link", problemDetails.Type);
Assert.Equal("Summary", problemDetails.Title);
Assert.Null(problemDetails.Detail);
Assert.Null(problemDetails.Instance);
}
}
}

View File

@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
@ -1081,7 +1082,7 @@ Environment.NewLine + "int b";
var context = GetContext(typeof(TestApiController));
var options = new ApiBehaviorOptions
{
SuppressUseClientErrorFactory = true,
SuppressMapClientErrors = true,
InvalidModelStateResponseFactory = _ => null,
};
var provider = GetProvider(options);
@ -1122,7 +1123,11 @@ Environment.NewLine + "int b";
var loggerFactory = NullLoggerFactory.Instance;
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
return new ApiBehaviorApplicationModelProvider(optionsAccessor, modelMetadataProvider, loggerFactory);
return new ApiBehaviorApplicationModelProvider(
optionsAccessor,
modelMetadataProvider,
Mock.Of<IClientErrorFactory>(),
loggerFactory);
}
private static ApplicationModelProviderContext GetContext(

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public void Configure_AddsClientErrorFactories()
public void Configure_AddsClientErrorMappings()
{
// Arrange
var expected = new[] { 400, 401, 403, 404, 406, 409, 415, 422, };
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
optionsSetup.Configure(options);
// Assert
Assert.Equal(expected, options.ClientErrorFactory.Keys);
Assert.Equal(expected, options.ClientErrorMapping.Keys);
}
[Fact]

View File

@ -97,6 +97,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert
await response.AssertStatusCodeAsync(HttpStatusCode.UnsupportedMediaType);
var content = await response.Content.ReadAsStringAsync();
var problemDetails = JsonConvert.DeserializeObject<ProblemDetails>(content);
Assert.Equal((int)HttpStatusCode.UnsupportedMediaType, problemDetails.Status);
Assert.Equal("Unsupported Media Type", problemDetails.Title);
}
[Fact]

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(mvcOptions.EnableEndpointRouting);
Assert.Null(mvcOptions.MaxValidationDepth);
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
}
[Fact]
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(mvcOptions.EnableEndpointRouting);
Assert.Null(mvcOptions.MaxValidationDepth);
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
}
[Fact]
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
}
[Fact]
@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
}
// This just does the minimum needed to be able to resolve these options.