parent
17d72c2b94
commit
667ad4daff
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue