Merge release/2.2
This commit is contained in:
commit
f34634f9a4
|
|
@ -48,8 +48,8 @@
|
||||||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-a-alpha1-master-routing-refactor-16895</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||||
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRoutingPackageVersion>
|
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-a-alpha1-master-routing-refactor-16895</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||||
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreSessionPackageVersion>
|
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreSessionPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
@ -10,17 +13,31 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options used to configure behavior for types annotated with <see cref="ApiControllerAttribute"/>.
|
/// Options used to configure behavior for types annotated with <see cref="ApiControllerAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiBehaviorOptions
|
public class ApiBehaviorOptions : IEnumerable<ICompatibilitySwitch>
|
||||||
{
|
{
|
||||||
|
private readonly CompatibilitySwitch<bool> _suppressUseClientErrorFactory;
|
||||||
|
private readonly CompatibilitySwitch<bool> _suppressUseValidationProblemDetailsForInvalidModelStateResponses;
|
||||||
|
private readonly ICompatibilitySwitch[] _switches;
|
||||||
|
|
||||||
private Func<ActionContext, IActionResult> _invalidModelStateResponseFactory;
|
private Func<ActionContext, IActionResult> _invalidModelStateResponseFactory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ApiBehaviorOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ApiBehaviorOptions()
|
||||||
|
{
|
||||||
|
_suppressUseClientErrorFactory = new CompatibilitySwitch<bool>(nameof(SuppressUseClientErrorFactory));
|
||||||
|
_suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch<bool>(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses));
|
||||||
|
_switches = new[]
|
||||||
|
{
|
||||||
|
_suppressUseClientErrorFactory,
|
||||||
|
_suppressUseValidationProblemDetailsForInvalidModelStateResponses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate invoked on actions annotated with <see cref="ApiControllerAttribute"/> to convert invalid
|
/// Delegate invoked on actions annotated with <see cref="ApiControllerAttribute"/> to convert invalid
|
||||||
/// <see cref="ModelStateDictionary"/> into an <see cref="IActionResult"/>
|
/// <see cref="ModelStateDictionary"/> into an <see cref="IActionResult"/>
|
||||||
/// <para>
|
|
||||||
/// By default, the delegate produces a <see cref="BadRequestObjectResult"/> that wraps a serialized form
|
|
||||||
/// of <see cref="ModelStateDictionary"/>.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<ActionContext, IActionResult> InvalidModelStateResponseFactory
|
public Func<ActionContext, IActionResult> InvalidModelStateResponseFactory
|
||||||
{
|
{
|
||||||
|
|
@ -52,5 +69,96 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// that are bound from form data.
|
/// that are bound from form data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SuppressConsumesConstraintForFormFileParameters { get; set; }
|
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.
|
||||||
|
/// <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"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The default value is <see langword="true"/> if the version is
|
||||||
|
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="false"/> otherwise.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This property is associated with a compatibility switch and can provide a different behavior depending on
|
||||||
|
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
|
||||||
|
/// guidance and examples of setting the application's compatibility version.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
|
||||||
|
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
|
||||||
|
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
|
||||||
|
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public bool SuppressUseClientErrorFactory
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that determines if controllers annotated with <see cref="ApiControllerAttribute"/> respond using
|
||||||
|
/// <see cref="ValidationProblemDetails"/> in <see cref="InvalidModelStateResponseFactory"/>.
|
||||||
|
/// <para>
|
||||||
|
/// When <see langword="true"/>, <see cref="SuppressModelStateInvalidFilter"/> returns errors in <see cref="ModelStateDictionary"/>
|
||||||
|
/// as a <see cref="ValidationProblemDetails"/>. Otherwise, <see cref="SuppressModelStateInvalidFilter"/> returns the errors
|
||||||
|
/// in the format determined by <see cref="SerializableError"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The default value is <see langword="true"/> if the version is
|
||||||
|
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="false"/> otherwise.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This property is associated with a compatibility switch and can provide a different behavior depending on
|
||||||
|
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
|
||||||
|
/// guidance and examples of setting the application's compatibility version.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
|
||||||
|
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
|
||||||
|
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
|
||||||
|
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public bool SuppressUseValidationProblemDetailsForInvalidModelStateResponses
|
||||||
|
{
|
||||||
|
get => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value;
|
||||||
|
set => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<int, Func<ActionContext, IActionResult>> ClientErrorFactory { get; } =
|
||||||
|
new Dictionary<int, Func<ActionContext, IActionResult>>();
|
||||||
|
|
||||||
|
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
|
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Transient<IPostConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, MvcCoreRouteOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, MvcCoreRouteOptionsSetup>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
|
||||||
|
{
|
||||||
|
private readonly IDictionary<int, Func<ActionContext, IActionResult>> _clientErrorFactory;
|
||||||
|
private readonly ILogger<ClientErrorResultFilter> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filter order. Defaults to -2000 so that it runs early.
|
||||||
|
/// </summary>
|
||||||
|
public int Order => -2000;
|
||||||
|
|
||||||
|
public ClientErrorResultFilter(
|
||||||
|
ApiBehaviorOptions apiBehaviorOptions,
|
||||||
|
ILogger<ClientErrorResultFilter> logger)
|
||||||
|
{
|
||||||
|
_clientErrorFactory = apiBehaviorOptions?.ClientErrorFactory ?? throw new ArgumentNullException(nameof(apiBehaviorOptions));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResultExecuted(ResultExecutedContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResultExecuting(ResultExecutingContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Result is IClientErrorActionResult clientErrorActionResult &&
|
||||||
|
clientErrorActionResult.StatusCode is int statusCode &&
|
||||||
|
_clientErrorFactory.TryGetValue(statusCode, out var factory))
|
||||||
|
{
|
||||||
|
var result = factory(context);
|
||||||
|
|
||||||
|
_logger.TransformingClientError(context.Result.GetType(), result?.GetType(), statusCode);
|
||||||
|
|
||||||
|
context.Result = factory(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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>
|
||||||
|
/// An <see cref="IStatusCodeActionResult"/> that can be transformed to a more descriptive client error.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClientErrorActionResult : IStatusCodeActionResult
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private readonly ApiBehaviorOptions _apiBehaviorOptions;
|
private readonly ApiBehaviorOptions _apiBehaviorOptions;
|
||||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||||
private readonly ModelStateInvalidFilter _modelStateInvalidFilter;
|
private readonly ModelStateInvalidFilter _modelStateInvalidFilter;
|
||||||
|
private readonly ClientErrorResultFilter _clientErrorResultFilter;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ApiBehaviorApplicationModelProvider(
|
public ApiBehaviorApplicationModelProvider(
|
||||||
|
|
@ -42,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
_modelStateInvalidFilter = new ModelStateInvalidFilter(
|
_modelStateInvalidFilter = new ModelStateInvalidFilter(
|
||||||
apiBehaviorOptions.Value,
|
apiBehaviorOptions.Value,
|
||||||
loggerFactory.CreateLogger<ModelStateInvalidFilter>());
|
loggerFactory.CreateLogger<ModelStateInvalidFilter>());
|
||||||
|
|
||||||
|
_clientErrorResultFilter = new ClientErrorResultFilter(
|
||||||
|
_apiBehaviorOptions,
|
||||||
|
loggerFactory.CreateLogger<ClientErrorResultFilter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
@ -90,6 +95,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
AddInvalidModelStateFilter(actionModel);
|
AddInvalidModelStateFilter(actionModel);
|
||||||
|
|
||||||
|
AddClientErrorFilter(actionModel);
|
||||||
|
|
||||||
InferParameterBindingSources(actionModel);
|
InferParameterBindingSources(actionModel);
|
||||||
|
|
||||||
InferParameterModelPrefixes(actionModel);
|
InferParameterModelPrefixes(actionModel);
|
||||||
|
|
@ -149,6 +156,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
actionModel.Filters.Add(_modelStateInvalidFilter);
|
actionModel.Filters.Add(_modelStateInvalidFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddClientErrorFilter(ActionModel actionModel)
|
||||||
|
{
|
||||||
|
if (_apiBehaviorOptions.SuppressUseClientErrorFactory)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionModel.Filters.Add(_clientErrorResultFilter);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal for unit testing
|
// Internal for unit testing
|
||||||
internal void InferParameterBindingSources(ActionModel actionModel)
|
internal void InferParameterBindingSources(ActionModel actionModel)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,44 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ApiBehaviorOptionsSetup : IConfigureOptions<ApiBehaviorOptions>
|
public class ApiBehaviorOptionsSetup :
|
||||||
|
ConfigureCompatibilityOptions<ApiBehaviorOptions>,
|
||||||
|
IConfigureOptions<ApiBehaviorOptions>
|
||||||
{
|
{
|
||||||
|
internal static readonly Func<ActionContext, IActionResult> DefaultFactory = DefaultInvalidModelStateResponse;
|
||||||
|
internal static readonly Func<ActionContext, IActionResult> ProblemDetailsFactory = ProblemDetailsInvalidModelStateResponse;
|
||||||
|
|
||||||
public ApiBehaviorOptionsSetup()
|
public ApiBehaviorOptionsSetup(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IOptions<MvcCompatibilityOptions> compatibilityOptions)
|
||||||
|
: base(loggerFactory, compatibilityOptions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDictionary<string, object> DefaultValues
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dictionary = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
if (Version < CompatibilityVersion.Version_2_2)
|
||||||
|
{
|
||||||
|
dictionary[nameof(ApiBehaviorOptions.SuppressUseClientErrorFactory)] = true;
|
||||||
|
dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Configure(ApiBehaviorOptions options)
|
public void Configure(ApiBehaviorOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
|
|
@ -20,17 +47,117 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
options.InvalidModelStateResponseFactory = GetInvalidModelStateResponse;
|
options.InvalidModelStateResponseFactory = DefaultFactory;
|
||||||
|
ConfigureClientErrorFactories(options);
|
||||||
|
}
|
||||||
|
|
||||||
IActionResult GetInvalidModelStateResponse(ActionContext context)
|
public override void PostConfigure(string name, ApiBehaviorOptions options)
|
||||||
|
{
|
||||||
|
// Let compatibility switches do their thing.
|
||||||
|
base.PostConfigure(name, options);
|
||||||
|
|
||||||
|
// We want to use problem details factory only if
|
||||||
|
// (a) it has not been opted out of (SuppressUseClientErrorFactory = true)
|
||||||
|
// (b) a different factory was configured
|
||||||
|
if (!options.SuppressUseClientErrorFactory &&
|
||||||
|
object.ReferenceEquals(options.InvalidModelStateResponseFactory, DefaultFactory))
|
||||||
{
|
{
|
||||||
var result = new BadRequestObjectResult(context.ModelState);
|
options.InvalidModelStateResponseFactory = ProblemDetailsFactory;
|
||||||
|
|
||||||
result.ContentTypes.Add("application/json");
|
|
||||||
result.ContentTypes.Add("application/xml");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal for unit testing
|
||||||
|
internal static void ConfigureClientErrorFactories(ApiBehaviorOptions options)
|
||||||
|
{
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 400,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
||||||
|
Title = Resources.ApiConventions_Title_400,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 401,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7235#section-3.1",
|
||||||
|
Title = Resources.ApiConventions_Title_401,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 403,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3",
|
||||||
|
Title = Resources.ApiConventions_Title_403,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 404,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
|
||||||
|
Title = Resources.ApiConventions_Title_404,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 406,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.6",
|
||||||
|
Title = Resources.ApiConventions_Title_406,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 409,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
|
||||||
|
Title = Resources.ApiConventions_Title_409,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 415,
|
||||||
|
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.13",
|
||||||
|
Title = Resources.ApiConventions_Title_415,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddClientErrorFactory(new ProblemDetails
|
||||||
|
{
|
||||||
|
Status = 422,
|
||||||
|
Type = "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)
|
||||||
|
{
|
||||||
|
var result = new BadRequestObjectResult(context.ModelState);
|
||||||
|
|
||||||
|
result.ContentTypes.Add("application/json");
|
||||||
|
result.ContentTypes.Add("application/xml");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IActionResult ProblemDetailsInvalidModelStateResponse(ActionContext context)
|
||||||
|
{
|
||||||
|
var result = new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState));
|
||||||
|
|
||||||
|
result.ContentTypes.Add("application/problem+json");
|
||||||
|
result.ContentTypes.Add("application/problem+xml");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private static readonly Action<ILogger, Type, Type, Type, Exception> _notMostEffectiveFilter;
|
private static readonly Action<ILogger, Type, Type, Type, Exception> _notMostEffectiveFilter;
|
||||||
private static readonly Action<ILogger, IEnumerable<IOutputFormatter>, Exception> _registeredOutputFormatters;
|
private static readonly Action<ILogger, IEnumerable<IOutputFormatter>, Exception> _registeredOutputFormatters;
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Type, Type, int, Exception> _transformingClientError;
|
||||||
|
|
||||||
static MvcCoreLoggerExtensions()
|
static MvcCoreLoggerExtensions()
|
||||||
{
|
{
|
||||||
_actionExecuting = LoggerMessage.Define<string, string>(
|
_actionExecuting = LoggerMessage.Define<string, string>(
|
||||||
|
|
@ -648,6 +650,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
48,
|
48,
|
||||||
"Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request.");
|
"Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request.");
|
||||||
|
|
||||||
|
_transformingClientError = LoggerMessage.Define<Type, Type, int>(
|
||||||
|
LogLevel.Trace,
|
||||||
|
new EventId(49, nameof(Infrastructure.ClientErrorResultFilter)),
|
||||||
|
"Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType} produced from ClientErrorFactory'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable<IOutputFormatter> outputFormatters)
|
public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable<IOutputFormatter> outputFormatters)
|
||||||
|
|
@ -1578,6 +1585,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int statusCode)
|
||||||
|
{
|
||||||
|
_transformingClientError(logger, initialType, replacedType, statusCode, null);
|
||||||
|
}
|
||||||
|
|
||||||
private static void LogFilterExecutionPlan(
|
private static void LogFilterExecutionPlan(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
string filterType,
|
string filterType,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatcherEndpoint CreateEndpoint(
|
private RouteEndpoint CreateEndpoint(
|
||||||
ActionDescriptor action,
|
ActionDescriptor action,
|
||||||
string routeName,
|
string routeName,
|
||||||
string template,
|
string template,
|
||||||
|
|
@ -271,9 +271,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
object source,
|
object source,
|
||||||
bool suppressLinkGeneration)
|
bool suppressLinkGeneration)
|
||||||
{
|
{
|
||||||
RequestDelegate invokerDelegate = (context) =>
|
RequestDelegate requestDelegate = (context) =>
|
||||||
{
|
{
|
||||||
var values = context.Features.Get<IEndpointFeature>().Values;
|
var values = context.Features.Get<IRouteValuesFeature>().RouteValues;
|
||||||
var routeData = new RouteData();
|
var routeData = new RouteData();
|
||||||
foreach (var kvp in values)
|
foreach (var kvp in values)
|
||||||
{
|
{
|
||||||
|
|
@ -299,9 +299,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
source,
|
source,
|
||||||
suppressLinkGeneration);
|
suppressLinkGeneration);
|
||||||
|
|
||||||
var endpoint = new MatcherEndpoint(
|
var endpoint = new RouteEndpoint(
|
||||||
next => invokerDelegate,
|
requestDelegate,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
action.DisplayName);
|
action.DisplayName);
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
|
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
|
||||||
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
|
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
|
|
||||||
|
|
@ -1578,6 +1578,118 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
||||||
internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object p3)
|
internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object p3)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxPropertyDepth"), p0, p1, p2, p3);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxPropertyDepth"), p0, p1, p2, p3);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bad Request
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_400
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_400");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bad Request
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_400()
|
||||||
|
=> GetString("ApiConventions_Title_400");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unauthorized
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_401
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_401");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unauthorized
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_401()
|
||||||
|
=> GetString("ApiConventions_Title_401");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forbidden
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_403
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_403");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forbidden
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_403()
|
||||||
|
=> GetString("ApiConventions_Title_403");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not Found
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_404
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_404");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not Found
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_404()
|
||||||
|
=> GetString("ApiConventions_Title_404");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not Acceptable
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_406
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_406");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not Acceptable
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_406()
|
||||||
|
=> GetString("ApiConventions_Title_406");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Conflict
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_409
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_409");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Conflict
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_409()
|
||||||
|
=> GetString("ApiConventions_Title_409");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported Media Type
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_415
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_415");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsupported Media Type
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_415()
|
||||||
|
=> GetString("ApiConventions_Title_415");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unprocessable Entity
|
||||||
|
/// </summary>
|
||||||
|
internal static string ApiConventions_Title_422
|
||||||
|
{
|
||||||
|
get => GetString("ApiConventions_Title_422");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unprocessable Entity
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatApiConventions_Title_422()
|
||||||
|
=> GetString("ApiConventions_Title_422");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -466,4 +466,28 @@
|
||||||
<data name="ValidationVisitor_ExceededMaxPropertyDepth" xml:space="preserve">
|
<data name="ValidationVisitor_ExceededMaxPropertyDepth" xml:space="preserve">
|
||||||
<value>{0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.</value>
|
<value>{0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ApiConventions_Title_400" xml:space="preserve">
|
||||||
|
<value>Bad Request</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_401" xml:space="preserve">
|
||||||
|
<value>Unauthorized</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_403" xml:space="preserve">
|
||||||
|
<value>Forbidden</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_404" xml:space="preserve">
|
||||||
|
<value>Not Found</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_406" xml:space="preserve">
|
||||||
|
<value>Not Acceptable</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_409" xml:space="preserve">
|
||||||
|
<value>Conflict</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_415" xml:space="preserve">
|
||||||
|
<value>Unsupported Media Type</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApiConventions_Title_422" xml:space="preserve">
|
||||||
|
<value>Unprocessable Entity</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -135,8 +135,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
private Endpoint CreateRejectionEndpoint()
|
private Endpoint CreateRejectionEndpoint()
|
||||||
{
|
{
|
||||||
return new MatcherEndpoint(
|
return new RouteEndpoint(
|
||||||
(next) => (context) =>
|
(context) =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
|
context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
/// Represents an <see cref="ActionResult"/> that when executed will
|
/// Represents an <see cref="ActionResult"/> that when executed will
|
||||||
/// produce an HTTP response with the given response status code.
|
/// produce an HTTP response with the given response status code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatusCodeResult : ActionResult, IStatusCodeActionResult
|
public class StatusCodeResult : ActionResult, IClientErrorActionResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StatusCodeResult"/> class
|
/// Initializes a new instance of the <see cref="StatusCodeResult"/> class
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,13 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
typeof(ApiBehaviorOptionsSetup),
|
typeof(ApiBehaviorOptionsSetup),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
typeof(IPostConfigureOptions<ApiBehaviorOptions>),
|
||||||
|
new Type[]
|
||||||
|
{
|
||||||
|
typeof(ApiBehaviorOptionsSetup),
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
typeof(IActionConstraintProvider),
|
typeof(IActionConstraintProvider),
|
||||||
new Type[]
|
new Type[]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||||
|
{
|
||||||
|
public class ClientErrorResultFilterTest
|
||||||
|
{
|
||||||
|
private static readonly IActionResult Result = new EmptyResult();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnResultExecuting_DoesNothing_IfActionIsNotClientErrorActionResult()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionResult = new NotFoundObjectResult(new object());
|
||||||
|
var context = GetContext(actionResult);
|
||||||
|
var filter = GetFilter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
filter.OnResultExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(actionResult, context.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnResultExecuting_DoesNothing_IfStatusCodeDoesNotExistInApiBehaviorOptions()
|
||||||
|
{
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
filter.OnResultExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(actionResult, context.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnResultExecuting_TransformsClientErrors()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionResult = new NotFoundResult();
|
||||||
|
var context = GetContext(actionResult);
|
||||||
|
var filter = GetFilter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
filter.OnResultExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(Result, context.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClientErrorResultFilter GetFilter(ApiBehaviorOptions options = null)
|
||||||
|
{
|
||||||
|
var apiBehaviorOptions = options ?? GetOptions();
|
||||||
|
var filter = new ClientErrorResultFilter(apiBehaviorOptions, NullLogger<ClientErrorResultFilter>.Instance);
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApiBehaviorOptions GetOptions()
|
||||||
|
{
|
||||||
|
var apiBehaviorOptions = new ApiBehaviorOptions();
|
||||||
|
apiBehaviorOptions.ClientErrorFactory[404] = _ => Result;
|
||||||
|
return apiBehaviorOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResultExecutingContext GetContext(IActionResult actionResult)
|
||||||
|
{
|
||||||
|
return new ResultExecutingContext(
|
||||||
|
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
|
||||||
|
Array.Empty<IFilterMetadata>(),
|
||||||
|
actionResult,
|
||||||
|
new object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||||
Assert.IsType<ModelStateInvalidFilter>(actionModel.Filters.Last());
|
Assert.Single(actionModel.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -54,8 +54,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
provider.OnProvidersExecuting(context);
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var controllerModel = Assert.Single(context.Result.Controllers);
|
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), controllerModel.Filters.Select(f => f.GetType()));
|
Assert.Empty(actionModel.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -73,11 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName),
|
Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName),
|
||||||
action =>
|
action =>
|
||||||
{
|
{
|
||||||
Assert.Contains(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
Assert.Single(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
},
|
},
|
||||||
action =>
|
action =>
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
Assert.Empty(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,11 +101,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName),
|
Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName),
|
||||||
action =>
|
action =>
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
Assert.Empty(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
},
|
},
|
||||||
action =>
|
action =>
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
Assert.Empty(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1059,6 +1059,41 @@ Environment.NewLine + "int b";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_AddsClientErrorResultFilter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetContext(typeof(TestApiController));
|
||||||
|
var provider = GetProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||||
|
Assert.Single(actionModel.Filters.OfType<ClientErrorResultFilter>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnProvidersExecuting_DoesNotAddClientErrorResultFilter_IfFeatureIsDisabled()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = GetContext(typeof(TestApiController));
|
||||||
|
var options = new ApiBehaviorOptions
|
||||||
|
{
|
||||||
|
SuppressUseClientErrorFactory = true,
|
||||||
|
InvalidModelStateResponseFactory = _ => null,
|
||||||
|
};
|
||||||
|
var provider = GetProvider(options);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||||
|
Assert.Empty(actionModel.Filters.OfType<ClientErrorResultFilter>());
|
||||||
|
}
|
||||||
|
|
||||||
// A dynamically generated type in an assembly that has an ApiConventionAttribute.
|
// A dynamically generated type in an assembly that has an ApiConventionAttribute.
|
||||||
private static TypeBuilder CreateTestControllerType()
|
private static TypeBuilder CreateTestControllerType()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
// 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.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
{
|
||||||
|
public class ApiBehaviorOptionsSetupTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Configure_AssignsInvalidModelStateResponseFactory()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var optionsSetup = new ApiBehaviorOptionsSetup(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions()));
|
||||||
|
var options = new ApiBehaviorOptions();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(ApiBehaviorOptionsSetup.DefaultFactory, options.InvalidModelStateResponseFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Configure_AddsClientErrorFactories()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new[] { 400, 401, 403, 404, 406, 409, 415, 422, };
|
||||||
|
var optionsSetup = new ApiBehaviorOptionsSetup(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions()));
|
||||||
|
var options = new ApiBehaviorOptions();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, options.ClientErrorFactory.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PostConfigure_SetProblemDetailsModelStateResponseFactory()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var optionsSetup = new ApiBehaviorOptionsSetup(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest }));
|
||||||
|
var options = new ApiBehaviorOptions();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(ApiBehaviorOptionsSetup.ProblemDetailsFactory, options.InvalidModelStateResponseFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PostConfigure_DoesNotSetProblemDetailsFactoryWithLegacyCompatBehavior()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var optionsSetup = new ApiBehaviorOptionsSetup(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_1 }));
|
||||||
|
var options = new ApiBehaviorOptions();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(ApiBehaviorOptionsSetup.DefaultFactory, options.InvalidModelStateResponseFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PostConfigure_DoesNotSetProblemDetailsFactory_IfValueWasModified()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var optionsSetup = new ApiBehaviorOptionsSetup(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest }));
|
||||||
|
var options = new ApiBehaviorOptions();
|
||||||
|
Func<ActionContext, IActionResult> expected = _ => null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
// This is equivalent to user code updating the value via ConfigureOptions
|
||||||
|
options.InvalidModelStateResponseFactory = expected;
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(expected, options.InvalidModelStateResponseFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
|
|
||||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<RouteValuesAddressMetadata>();
|
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<RouteValuesAddressMetadata>();
|
||||||
Assert.NotNull(routeValuesAddressMetadata);
|
Assert.NotNull(routeValuesAddressMetadata);
|
||||||
|
|
@ -77,14 +77,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled()
|
public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var featureCollection = new FeatureCollection();
|
var endpointFeature = new EndpointFeature
|
||||||
featureCollection.Set<IEndpointFeature>(new EndpointFeature
|
|
||||||
{
|
{
|
||||||
Values = new RouteValueDictionary()
|
RouteValues = new RouteValueDictionary()
|
||||||
});
|
};
|
||||||
|
|
||||||
|
var featureCollection = new FeatureCollection();
|
||||||
|
featureCollection.Set<IEndpointFeature>(endpointFeature);
|
||||||
|
featureCollection.Set<IRouteValuesFeature>(endpointFeature);
|
||||||
|
|
||||||
var httpContextMock = new Mock<HttpContext>();
|
var httpContextMock = new Mock<HttpContext>();
|
||||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||||
|
|
@ -122,11 +125,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
|
|
||||||
var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask);
|
await matcherEndpoint.RequestDelegate(httpContextMock.Object);
|
||||||
|
|
||||||
invokerDelegate(httpContextMock.Object);
|
|
||||||
|
|
||||||
Assert.True(actionInvokerCalled);
|
Assert.True(actionInvokerCalled);
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var featureCollection = new FeatureCollection();
|
var featureCollection = new FeatureCollection();
|
||||||
featureCollection.Set<IEndpointFeature>(new EndpointFeature
|
featureCollection.Set<IEndpointFeature>(new EndpointFeature
|
||||||
{
|
{
|
||||||
Values = new RouteValueDictionary()
|
RouteValues = new RouteValueDictionary()
|
||||||
});
|
});
|
||||||
|
|
||||||
var httpContextMock = new Mock<HttpContext>();
|
var httpContextMock = new Mock<HttpContext>();
|
||||||
|
|
@ -199,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var inspectors = finalEndpointTemplates
|
var inspectors = finalEndpointTemplates
|
||||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText)))
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -226,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var inspectors = finalEndpointTemplates
|
var inspectors = finalEndpointTemplates
|
||||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText)))
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -250,8 +251,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(endpoints,
|
Assert.Collection(endpoints,
|
||||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -278,8 +279,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(endpoints1,
|
Assert.Collection(endpoints1,
|
||||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
Assert.Same(endpoints1, endpoints2);
|
Assert.Same(endpoints1, endpoints2);
|
||||||
|
|
||||||
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
|
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
|
||||||
|
|
@ -320,8 +321,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var endpoints = dataSource.Endpoints;
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
Assert.Collection(endpoints,
|
Assert.Collection(endpoints,
|
||||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
|
|
||||||
actionDescriptorCollectionProviderMock
|
actionDescriptorCollectionProviderMock
|
||||||
.Setup(m => m.ActionDescriptors)
|
.Setup(m => m.ActionDescriptors)
|
||||||
|
|
@ -337,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
Assert.NotSame(endpoints, newEndpoints);
|
Assert.NotSame(endpoints, newEndpoints);
|
||||||
Assert.Collection(newEndpoints,
|
Assert.Collection(newEndpoints,
|
||||||
(e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
(e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -359,8 +360,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(endpoints,
|
Assert.Collection(endpoints,
|
||||||
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||||
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -383,7 +384,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var endpoints = dataSource.Endpoints;
|
var endpoints = dataSource.Endpoints;
|
||||||
|
|
||||||
var inspectors = finalEndpointTemplates
|
var inspectors = finalEndpointTemplates
|
||||||
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText)))
|
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
Assert.NotNull(routeValuesAddressNameMetadata);
|
Assert.NotNull(routeValuesAddressNameMetadata);
|
||||||
Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name);
|
Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name);
|
||||||
|
|
@ -430,7 +431,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
endpoints,
|
endpoints,
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
Assert.NotNull(routeValuesAddressMetadata);
|
Assert.NotNull(routeValuesAddressMetadata);
|
||||||
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
||||||
|
|
@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
},
|
},
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||||
Assert.NotNull(routeValuesAddressMetadata);
|
Assert.NotNull(routeValuesAddressMetadata);
|
||||||
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
||||||
|
|
@ -469,25 +470,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
endpoints,
|
endpoints,
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||||
Assert.Equal(1, matcherEndpoint.Order);
|
Assert.Equal(1, matcherEndpoint.Order);
|
||||||
},
|
},
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||||
Assert.Equal(2, matcherEndpoint.Order);
|
Assert.Equal(2, matcherEndpoint.Order);
|
||||||
},
|
},
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||||
Assert.Equal(1, matcherEndpoint.Order);
|
Assert.Equal(1, matcherEndpoint.Order);
|
||||||
},
|
},
|
||||||
(ep) =>
|
(ep) =>
|
||||||
{
|
{
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||||
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||||
Assert.Equal(2, matcherEndpoint.Order);
|
Assert.Equal(2, matcherEndpoint.Order);
|
||||||
});
|
});
|
||||||
|
|
@ -589,7 +590,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +612,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||||
}
|
}
|
||||||
|
|
@ -634,7 +635,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
|
||||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||||
}
|
}
|
||||||
|
|
@ -656,7 +657,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var endpoint = Assert.Single(endpoints);
|
var endpoint = Assert.Single(endpoints);
|
||||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||||
|
|
@ -369,11 +370,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
return httpContext;
|
return httpContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MatcherEndpoint CreateEndpoint(ActionDescriptor action)
|
private static RouteEndpoint CreateEndpoint(ActionDescriptor action)
|
||||||
{
|
{
|
||||||
var metadata = new List<object>() { action, };
|
var metadata = new List<object>() { action, };
|
||||||
return new MatcherEndpoint(
|
return new RouteEndpoint(
|
||||||
(r) => null,
|
(context) => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse("/"),
|
RoutePatternFactory.Parse("/"),
|
||||||
0,
|
0,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
|
|
@ -212,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MatcherEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata)
|
private static RouteEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata)
|
||||||
{
|
{
|
||||||
var metadata = new List<object>();
|
var metadata = new List<object>();
|
||||||
if (consumesMetadata != null)
|
if (consumesMetadata != null)
|
||||||
|
|
@ -220,8 +221,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
metadata.Add(consumesMetadata);
|
metadata.Add(consumesMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatcherEndpoint(
|
return new RouteEndpoint(
|
||||||
(next) => null,
|
(context) => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
0,
|
0,
|
||||||
new EndpointMetadataCollection(metadata),
|
new EndpointMetadataCollection(metadata),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Matching;
|
using Microsoft.AspNetCore.Routing.Matching;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
@ -60,8 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
// Set the endpoint feature and current context just as a normal request to MVC app would be
|
// Set the endpoint feature and current context just as a normal request to MVC app would be
|
||||||
var endpointFeature = new EndpointFeature();
|
var endpointFeature = new EndpointFeature();
|
||||||
urlHelper.ActionContext.HttpContext.Features.Set<IEndpointFeature>(endpointFeature);
|
urlHelper.ActionContext.HttpContext.Features.Set<IEndpointFeature>(endpointFeature);
|
||||||
|
urlHelper.ActionContext.HttpContext.Features.Set<IRouteValuesFeature>(endpointFeature);
|
||||||
endpointFeature.Endpoint = endpoint1;
|
endpointFeature.Endpoint = endpoint1;
|
||||||
endpointFeature.Values = new RouteValueDictionary
|
endpointFeature.RouteValues = new RouteValueDictionary
|
||||||
{
|
{
|
||||||
["controller"] = "Orders",
|
["controller"] = "Orders",
|
||||||
["action"] = "GetById",
|
["action"] = "GetById",
|
||||||
|
|
@ -123,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol)
|
protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol)
|
||||||
{
|
{
|
||||||
return CreateUrlHelper(Enumerable.Empty<MatcherEndpoint>(), appRoot, host, protocol);
|
return CreateUrlHelper(Enumerable.Empty<RouteEndpoint>(), appRoot, host, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IUrlHelper CreateUrlHelperWithDefaultRoutes(string appRoot, string host, string protocol)
|
protected override IUrlHelper CreateUrlHelperWithDefaultRoutes(string appRoot, string host, string protocol)
|
||||||
|
|
@ -139,8 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
string template)
|
string template)
|
||||||
{
|
{
|
||||||
var endpoints = GetDefaultEndpoints();
|
var endpoints = GetDefaultEndpoints();
|
||||||
endpoints.Add(new MatcherEndpoint(
|
endpoints.Add(new RouteEndpoint(
|
||||||
next => httpContext => Task.CompletedTask,
|
httpContext => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse(template),
|
RoutePatternFactory.Parse(template),
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
|
|
@ -153,10 +156,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
var httpContext = actionContext.HttpContext;
|
var httpContext = actionContext.HttpContext;
|
||||||
httpContext.Features.Set<IEndpointFeature>(new EndpointFeature()
|
httpContext.Features.Set<IEndpointFeature>(new EndpointFeature()
|
||||||
{
|
{
|
||||||
Endpoint = new MatcherEndpoint(
|
Endpoint = new Endpoint(
|
||||||
next => cntxt => Task.CompletedTask,
|
context => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse("/"),
|
|
||||||
0,
|
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
null)
|
null)
|
||||||
});
|
});
|
||||||
|
|
@ -187,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
return CreateUrlHelper(actionContext);
|
return CreateUrlHelper(actionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IUrlHelper CreateUrlHelper(IEnumerable<MatcherEndpoint> endpoints, ActionContext actionContext = null)
|
private IUrlHelper CreateUrlHelper(IEnumerable<RouteEndpoint> endpoints, ActionContext actionContext = null)
|
||||||
{
|
{
|
||||||
var serviceProvider = CreateServices(endpoints);
|
var serviceProvider = CreateServices(endpoints);
|
||||||
var httpContext = CreateHttpContext(serviceProvider, null, null, "http");
|
var httpContext = CreateHttpContext(serviceProvider, null, null, "http");
|
||||||
|
|
@ -196,7 +197,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
private IUrlHelper CreateUrlHelper(
|
private IUrlHelper CreateUrlHelper(
|
||||||
IEnumerable<MatcherEndpoint> endpoints,
|
IEnumerable<RouteEndpoint> endpoints,
|
||||||
string appRoot,
|
string appRoot,
|
||||||
string host,
|
string host,
|
||||||
string protocol)
|
string protocol)
|
||||||
|
|
@ -207,9 +208,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
return CreateUrlHelper(actionContext);
|
return CreateUrlHelper(actionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MatcherEndpoint> GetDefaultEndpoints()
|
private List<RouteEndpoint> GetDefaultEndpoints()
|
||||||
{
|
{
|
||||||
var endpoints = new List<MatcherEndpoint>();
|
var endpoints = new List<RouteEndpoint>();
|
||||||
endpoints.Add(
|
endpoints.Add(
|
||||||
CreateEndpoint(
|
CreateEndpoint(
|
||||||
"home/newaction/{id}",
|
"home/newaction/{id}",
|
||||||
|
|
@ -278,7 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
return endpoints;
|
return endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatcherEndpoint CreateEndpoint(
|
private RouteEndpoint CreateEndpoint(
|
||||||
string template,
|
string template,
|
||||||
object defaults = null,
|
object defaults = null,
|
||||||
object requiredValues = null,
|
object requiredValues = null,
|
||||||
|
|
@ -292,9 +293,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MatcherEndpoint(
|
return new RouteEndpoint(
|
||||||
next => (httpContext) => Task.CompletedTask,
|
(httpContext) => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||||
order,
|
order,
|
||||||
metadataCollection,
|
metadataCollection,
|
||||||
null);
|
null);
|
||||||
|
|
@ -315,11 +316,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatcherEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults)
|
private RouteEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults)
|
||||||
{
|
{
|
||||||
return new MatcherEndpoint(
|
return new RouteEndpoint(
|
||||||
next => c => Task.CompletedTask,
|
c => Task.CompletedTask,
|
||||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||||
0,
|
0,
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
null);
|
null);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BasicWebSite.Models;
|
using BasicWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -18,9 +19,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
public ApiBehaviorTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
public ApiBehaviorTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
{
|
{
|
||||||
Client = fixture.CreateDefaultClient();
|
Client = fixture.CreateDefaultClient();
|
||||||
|
|
||||||
|
var factory = fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||||
|
CustomInvalidModelStateClient = factory.CreateDefaultClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||||
|
builder.UseStartup<BasicWebSite.StartupWithCustomInvalidModelStateFactory>();
|
||||||
|
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
public HttpClient CustomInvalidModelStateClient { get; }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ActionsReturnBadRequest_WhenModelStateIsInvalid()
|
public async Task ActionsReturnBadRequest_WhenModelStateIsInvalid()
|
||||||
|
|
@ -39,11 +47,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var response = await Client.PostAsJsonAsync("/contact", contactModel);
|
var response = await Client.PostAsJsonAsync("/contact", contactModel);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType);
|
||||||
var actual = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(await response.Content.ReadAsStringAsync());
|
var problemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(await response.Content.ReadAsStringAsync());
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
actual.OrderBy(kvp => kvp.Key),
|
problemDetails.Errors.OrderBy(kvp => kvp.Key),
|
||||||
kvp =>
|
kvp =>
|
||||||
{
|
{
|
||||||
Assert.Equal("Name", kvp.Key);
|
Assert.Equal("Name", kvp.Key);
|
||||||
|
|
@ -110,10 +118,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var contactString = JsonConvert.SerializeObject(contactModel);
|
var contactString = JsonConvert.SerializeObject(contactModel);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.PostAsJsonAsync("/contact/PostWithVnd", contactModel);
|
var response = await CustomInvalidModelStateClient.PostAsJsonAsync("/contact/PostWithVnd", contactModel);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
Assert.Equal("application/vnd.error+json", response.Content.Headers.ContentType.MediaType);
|
Assert.Equal("application/vnd.error+json", response.Content.Headers.ContentType.MediaType);
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
var actual = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(content);
|
var actual = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(content);
|
||||||
|
|
@ -236,5 +244,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var result = await response.Content.ReadAsStringAsync();
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
Assert.Equal(expected, result);
|
Assert.Equal(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientErrorResultFilterExecutesForStatusCodeResults()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.NotFound);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var problemDetails = JsonConvert.DeserializeObject<ProblemDetails>(content);
|
||||||
|
Assert.Equal(404, problemDetails.Status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -41,6 +42,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.False(razorPagesOptions.AllowAreas);
|
Assert.False(razorPagesOptions.AllowAreas);
|
||||||
Assert.False(mvcOptions.EnableEndpointRouting);
|
Assert.False(mvcOptions.EnableEndpointRouting);
|
||||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||||
|
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
|
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -57,6 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -67,6 +71,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.True(razorPagesOptions.AllowAreas);
|
Assert.True(razorPagesOptions.AllowAreas);
|
||||||
Assert.False(mvcOptions.EnableEndpointRouting);
|
Assert.False(mvcOptions.EnableEndpointRouting);
|
||||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||||
|
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
|
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -83,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -93,6 +100,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.True(razorPagesOptions.AllowAreas);
|
Assert.True(razorPagesOptions.AllowAreas);
|
||||||
Assert.True(mvcOptions.EnableEndpointRouting);
|
Assert.True(mvcOptions.EnableEndpointRouting);
|
||||||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||||
|
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
|
Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -109,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -119,6 +129,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.True(razorPagesOptions.AllowAreas);
|
Assert.True(razorPagesOptions.AllowAreas);
|
||||||
Assert.True(mvcOptions.EnableEndpointRouting);
|
Assert.True(mvcOptions.EnableEndpointRouting);
|
||||||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||||
|
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
|
Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This just does the minimum needed to be able to resolve these options.
|
// This just does the minimum needed to be able to resolve these options.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BasicWebSite.Models;
|
using BasicWebSite.Models;
|
||||||
|
|
@ -88,6 +85,12 @@ namespace BasicWebSite
|
||||||
return foo;
|
return foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("[action]")]
|
||||||
|
public ActionResult<int> ActionReturningStatusCodeResult()
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
private class TestModelBinder : IModelBinder
|
private class TestModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace BasicWebSite
|
namespace BasicWebSite
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
@ -33,22 +32,6 @@ namespace BasicWebSite
|
||||||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||||
.AddXmlDataContractSerializerFormatters();
|
.AddXmlDataContractSerializerFormatters();
|
||||||
|
|
||||||
services.Configure<ApiBehaviorOptions>(options =>
|
|
||||||
{
|
|
||||||
var previous = options.InvalidModelStateResponseFactory;
|
|
||||||
options.InvalidModelStateResponseFactory = context =>
|
|
||||||
{
|
|
||||||
var result = (BadRequestObjectResult)previous(context);
|
|
||||||
if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute))
|
|
||||||
{
|
|
||||||
result.ContentTypes.Clear();
|
|
||||||
result.ContentTypes.Add("application/vnd.error+json");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
services.ConfigureBaseWebSiteAuthPolicies();
|
services.ConfigureBaseWebSiteAuthPolicies();
|
||||||
|
|
||||||
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
services.AddTransient<IAuthorizationHandler, ManagerHandler>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BasicWebSite
|
||||||
|
{
|
||||||
|
public class StartupWithCustomInvalidModelStateFactory
|
||||||
|
{
|
||||||
|
// Set up application services
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddAuthentication()
|
||||||
|
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Api", _ => { });
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddMvc()
|
||||||
|
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||||
|
|
||||||
|
services.Configure<ApiBehaviorOptions>(options =>
|
||||||
|
{
|
||||||
|
var previous = options.InvalidModelStateResponseFactory;
|
||||||
|
options.InvalidModelStateResponseFactory = context =>
|
||||||
|
{
|
||||||
|
var result = (BadRequestObjectResult)previous(context);
|
||||||
|
if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute))
|
||||||
|
{
|
||||||
|
result.ContentTypes.Clear();
|
||||||
|
result.ContentTypes.Add("application/vnd.error+json");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.ConfigureBaseWebSiteAuthPolicies();
|
||||||
|
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddSingleton<ContactsRepository>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace RoutingWebSite
|
namespace RoutingWebSite
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace VersioningWebSite
|
namespace VersioningWebSite
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue