Merge release/2.2
This commit is contained in:
commit
f34634f9a4
|
|
@ -48,8 +48,8 @@
|
|||
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreResponseCachingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-a-alpha1-master-routing-refactor-16895</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-a-alpha1-master-routing-refactor-16895</MicrosoftAspNetCoreRoutingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10321</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<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.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
|
|
@ -10,17 +13,31 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// <summary>
|
||||
/// Options used to configure behavior for types annotated with <see cref="ApiControllerAttribute"/>.
|
||||
/// </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;
|
||||
|
||||
/// <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>
|
||||
/// Delegate invoked on actions annotated with <see cref="ApiControllerAttribute"/> to convert invalid
|
||||
/// <see cref="ModelStateDictionary"/> into an <see cref="IActionResult"/>
|
||||
/// <para>
|
||||
/// By default, the delegate produces a <see cref="BadRequestObjectResult"/> that wraps a serialized form
|
||||
/// of <see cref="ModelStateDictionary"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Func<ActionContext, IActionResult> InvalidModelStateResponseFactory
|
||||
{
|
||||
|
|
@ -52,5 +69,96 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// that are bound from form data.
|
||||
/// </summary>
|
||||
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>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IPostConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
||||
services.TryAddEnumerable(
|
||||
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 IModelMetadataProvider _modelMetadataProvider;
|
||||
private readonly ModelStateInvalidFilter _modelStateInvalidFilter;
|
||||
private readonly ClientErrorResultFilter _clientErrorResultFilter;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ApiBehaviorApplicationModelProvider(
|
||||
|
|
@ -42,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
_modelStateInvalidFilter = new ModelStateInvalidFilter(
|
||||
apiBehaviorOptions.Value,
|
||||
loggerFactory.CreateLogger<ModelStateInvalidFilter>());
|
||||
|
||||
_clientErrorResultFilter = new ClientErrorResultFilter(
|
||||
_apiBehaviorOptions,
|
||||
loggerFactory.CreateLogger<ClientErrorResultFilter>());
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
|
@ -90,6 +95,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
AddInvalidModelStateFilter(actionModel);
|
||||
|
||||
AddClientErrorFilter(actionModel);
|
||||
|
||||
InferParameterBindingSources(actionModel);
|
||||
|
||||
InferParameterModelPrefixes(actionModel);
|
||||
|
|
@ -149,6 +156,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionModel.Filters.Add(_modelStateInvalidFilter);
|
||||
}
|
||||
|
||||
private void AddClientErrorFilter(ActionModel actionModel)
|
||||
{
|
||||
if (_apiBehaviorOptions.SuppressUseClientErrorFactory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
actionModel.Filters.Add(_clientErrorResultFilter);
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
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)
|
||||
{
|
||||
if (options == null)
|
||||
|
|
@ -20,17 +47,117 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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);
|
||||
|
||||
result.ContentTypes.Add("application/json");
|
||||
result.ContentTypes.Add("application/xml");
|
||||
|
||||
return result;
|
||||
options.InvalidModelStateResponseFactory = ProblemDetailsFactory;
|
||||
}
|
||||
}
|
||||
|
||||
// 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, IEnumerable<IOutputFormatter>, Exception> _registeredOutputFormatters;
|
||||
|
||||
private static readonly Action<ILogger, Type, Type, int, Exception> _transformingClientError;
|
||||
|
||||
static MvcCoreLoggerExtensions()
|
||||
{
|
||||
_actionExecuting = LoggerMessage.Define<string, string>(
|
||||
|
|
@ -648,6 +650,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
LogLevel.Debug,
|
||||
48,
|
||||
"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)
|
||||
|
|
@ -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(
|
||||
ILogger logger,
|
||||
string filterType,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return false;
|
||||
}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
ActionDescriptor action,
|
||||
string routeName,
|
||||
string template,
|
||||
|
|
@ -271,9 +271,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
object source,
|
||||
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();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
|
|
@ -299,9 +299,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
source,
|
||||
suppressLinkGeneration);
|
||||
|
||||
var endpoint = new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||
var endpoint = new RouteEndpoint(
|
||||
requestDelegate,
|
||||
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||
order,
|
||||
metadataCollection,
|
||||
action.DisplayName);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// 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_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.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
|
|
|
|||
|
|
@ -1578,6 +1578,118 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object 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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -466,4 +466,28 @@
|
|||
<data name="ValidationVisitor_ExceededMaxPropertyDepth" xml:space="preserve">
|
||||
<value>{0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.</value>
|
||||
</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>
|
||||
|
|
@ -135,8 +135,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
private Endpoint CreateRejectionEndpoint()
|
||||
{
|
||||
return new MatcherEndpoint(
|
||||
(next) => (context) =>
|
||||
return new RouteEndpoint(
|
||||
(context) =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Represents an <see cref="ActionResult"/> that when executed will
|
||||
/// produce an HTTP response with the given response status code.
|
||||
/// </summary>
|
||||
public class StatusCodeResult : ActionResult, IStatusCodeActionResult
|
||||
public class StatusCodeResult : ActionResult, IClientErrorActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StatusCodeResult"/> class
|
||||
|
|
|
|||
|
|
@ -264,6 +264,13 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
typeof(ApiBehaviorOptionsSetup),
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(IPostConfigureOptions<ApiBehaviorOptions>),
|
||||
new Type[]
|
||||
{
|
||||
typeof(ApiBehaviorOptionsSetup),
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(IActionConstraintProvider),
|
||||
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
|
||||
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||
Assert.IsType<ModelStateInvalidFilter>(actionModel.Filters.Last());
|
||||
Assert.Single(actionModel.Filters.OfType<ModelStateInvalidFilter>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -54,8 +54,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var controllerModel = Assert.Single(context.Result.Controllers);
|
||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), controllerModel.Filters.Select(f => f.GetType()));
|
||||
var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions);
|
||||
Assert.Empty(actionModel.Filters.OfType<ModelStateInvalidFilter>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -73,11 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName),
|
||||
action =>
|
||||
{
|
||||
Assert.Contains(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
||||
Assert.Single(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||
},
|
||||
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),
|
||||
action =>
|
||||
{
|
||||
Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType()));
|
||||
Assert.Empty(action.Filters.OfType<ModelStateInvalidFilter>());
|
||||
},
|
||||
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.
|
||||
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
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
|
||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<RouteValuesAddressMetadata>();
|
||||
Assert.NotNull(routeValuesAddressMetadata);
|
||||
|
|
@ -77,14 +77,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled()
|
||||
public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled()
|
||||
{
|
||||
// Arrange
|
||||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(new EndpointFeature
|
||||
var endpointFeature = 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>();
|
||||
httpContextMock.Setup(m => m.Features).Returns(featureCollection);
|
||||
|
|
@ -122,11 +125,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
|
||||
var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask);
|
||||
|
||||
invokerDelegate(httpContextMock.Object);
|
||||
await matcherEndpoint.RequestDelegate(httpContextMock.Object);
|
||||
|
||||
Assert.True(actionInvokerCalled);
|
||||
}
|
||||
|
|
@ -138,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var featureCollection = new FeatureCollection();
|
||||
featureCollection.Set<IEndpointFeature>(new EndpointFeature
|
||||
{
|
||||
Values = new RouteValueDictionary()
|
||||
RouteValues = new RouteValueDictionary()
|
||||
});
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
|
|
@ -199,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
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();
|
||||
|
||||
// Assert
|
||||
|
|
@ -226,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
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();
|
||||
|
||||
// Assert
|
||||
|
|
@ -250,8 +251,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
Assert.Collection(endpoints,
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -278,8 +279,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
Assert.Collection(endpoints1,
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
Assert.Same(endpoints1, endpoints2);
|
||||
|
||||
actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once);
|
||||
|
|
@ -320,8 +321,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
Assert.Collection(endpoints,
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
||||
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
|
||||
actionDescriptorCollectionProviderMock
|
||||
.Setup(m => m.ActionDescriptors)
|
||||
|
|
@ -337,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
Assert.NotSame(endpoints, 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]
|
||||
|
|
@ -359,8 +360,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
Assert.Collection(endpoints,
|
||||
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<MatcherEndpoint>(e).RoutePattern.RawText));
|
||||
(e) => Assert.Equal("TestController/TestAction1", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
|
||||
(e) => Assert.Equal("TestController/TestAction2", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -383,7 +384,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var endpoints = dataSource.Endpoints;
|
||||
|
||||
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();
|
||||
|
||||
// Assert
|
||||
|
|
@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
Assert.NotNull(routeValuesAddressNameMetadata);
|
||||
Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name);
|
||||
|
|
@ -430,7 +431,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
Assert.NotNull(routeValuesAddressMetadata);
|
||||
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
||||
|
|
@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
Assert.NotNull(routeValuesAddressMetadata);
|
||||
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name);
|
||||
|
|
@ -469,25 +470,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
endpoints,
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal(1, matcherEndpoint.Order);
|
||||
},
|
||||
(ep) =>
|
||||
{
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(ep);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
|
||||
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
|
||||
Assert.Equal(2, matcherEndpoint.Order);
|
||||
});
|
||||
|
|
@ -589,7 +590,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||
}
|
||||
|
|
@ -611,7 +612,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||
}
|
||||
|
|
@ -634,7 +635,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
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);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||
}
|
||||
|
|
@ -656,7 +657,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
// Assert
|
||||
var endpoint = Assert.Single(endpoints);
|
||||
var matcherEndpoint = Assert.IsType<MatcherEndpoint>(endpoint);
|
||||
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
|
||||
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
|
||||
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
|
|
@ -369,11 +370,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
return httpContext;
|
||||
}
|
||||
|
||||
private static MatcherEndpoint CreateEndpoint(ActionDescriptor action)
|
||||
private static RouteEndpoint CreateEndpoint(ActionDescriptor action)
|
||||
{
|
||||
var metadata = new List<object>() { action, };
|
||||
return new MatcherEndpoint(
|
||||
(r) => null,
|
||||
return new RouteEndpoint(
|
||||
(context) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse("/"),
|
||||
0,
|
||||
new EndpointMetadataCollection(metadata),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
|
@ -212,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
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>();
|
||||
if (consumesMetadata != null)
|
||||
|
|
@ -220,8 +221,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
metadata.Add(consumesMetadata);
|
||||
}
|
||||
|
||||
return new MatcherEndpoint(
|
||||
(next) => null,
|
||||
return new RouteEndpoint(
|
||||
(context) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template),
|
||||
0,
|
||||
new EndpointMetadataCollection(metadata),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
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
|
||||
var endpointFeature = new EndpointFeature();
|
||||
urlHelper.ActionContext.HttpContext.Features.Set<IEndpointFeature>(endpointFeature);
|
||||
urlHelper.ActionContext.HttpContext.Features.Set<IRouteValuesFeature>(endpointFeature);
|
||||
endpointFeature.Endpoint = endpoint1;
|
||||
endpointFeature.Values = new RouteValueDictionary
|
||||
endpointFeature.RouteValues = new RouteValueDictionary
|
||||
{
|
||||
["controller"] = "Orders",
|
||||
["action"] = "GetById",
|
||||
|
|
@ -123,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
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)
|
||||
|
|
@ -139,8 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
string template)
|
||||
{
|
||||
var endpoints = GetDefaultEndpoints();
|
||||
endpoints.Add(new MatcherEndpoint(
|
||||
next => httpContext => Task.CompletedTask,
|
||||
endpoints.Add(new RouteEndpoint(
|
||||
httpContext => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
|
|
@ -153,10 +156,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
var httpContext = actionContext.HttpContext;
|
||||
httpContext.Features.Set<IEndpointFeature>(new EndpointFeature()
|
||||
{
|
||||
Endpoint = new MatcherEndpoint(
|
||||
next => cntxt => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse("/"),
|
||||
0,
|
||||
Endpoint = new Endpoint(
|
||||
context => Task.CompletedTask,
|
||||
EndpointMetadataCollection.Empty,
|
||||
null)
|
||||
});
|
||||
|
|
@ -187,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
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 httpContext = CreateHttpContext(serviceProvider, null, null, "http");
|
||||
|
|
@ -196,7 +197,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
}
|
||||
|
||||
private IUrlHelper CreateUrlHelper(
|
||||
IEnumerable<MatcherEndpoint> endpoints,
|
||||
IEnumerable<RouteEndpoint> endpoints,
|
||||
string appRoot,
|
||||
string host,
|
||||
string protocol)
|
||||
|
|
@ -207,9 +208,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
return CreateUrlHelper(actionContext);
|
||||
}
|
||||
|
||||
private List<MatcherEndpoint> GetDefaultEndpoints()
|
||||
private List<RouteEndpoint> GetDefaultEndpoints()
|
||||
{
|
||||
var endpoints = new List<MatcherEndpoint>();
|
||||
var endpoints = new List<RouteEndpoint>();
|
||||
endpoints.Add(
|
||||
CreateEndpoint(
|
||||
"home/newaction/{id}",
|
||||
|
|
@ -278,7 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
return endpoints;
|
||||
}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
object requiredValues = null,
|
||||
|
|
@ -292,9 +293,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
||||
}
|
||||
|
||||
return new MatcherEndpoint(
|
||||
next => (httpContext) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||
return new RouteEndpoint(
|
||||
(httpContext) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||
order,
|
||||
metadataCollection,
|
||||
null);
|
||||
|
|
@ -315,11 +316,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private MatcherEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults)
|
||||
private RouteEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults)
|
||||
{
|
||||
return new MatcherEndpoint(
|
||||
next => c => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints: null),
|
||||
return new RouteEndpoint(
|
||||
c => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
null);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -18,9 +19,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public ApiBehaviorTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||
{
|
||||
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 CustomInvalidModelStateClient { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task ActionsReturnBadRequest_WhenModelStateIsInvalid()
|
||||
|
|
@ -39,11 +47,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.PostAsJsonAsync("/contact", contactModel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
|
||||
var actual = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(await response.Content.ReadAsStringAsync());
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||
Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType);
|
||||
var problemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(await response.Content.ReadAsStringAsync());
|
||||
Assert.Collection(
|
||||
actual.OrderBy(kvp => kvp.Key),
|
||||
problemDetails.Errors.OrderBy(kvp => kvp.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("Name", kvp.Key);
|
||||
|
|
@ -110,10 +118,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var contactString = JsonConvert.SerializeObject(contactModel);
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsJsonAsync("/contact/PostWithVnd", contactModel);
|
||||
var response = await CustomInvalidModelStateClient.PostAsJsonAsync("/contact/PostWithVnd", contactModel);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||
Assert.Equal("application/vnd.error+json", response.Content.Headers.ContentType.MediaType);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var actual = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(content);
|
||||
|
|
@ -236,5 +244,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var result = await response.Content.ReadAsStringAsync();
|
||||
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 jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||
|
|
@ -41,6 +42,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.False(razorPagesOptions.AllowAreas);
|
||||
Assert.False(mvcOptions.EnableEndpointRouting);
|
||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -57,6 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||
|
|
@ -67,6 +71,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.True(razorPagesOptions.AllowAreas);
|
||||
Assert.False(mvcOptions.EnableEndpointRouting);
|
||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||
|
|
@ -93,6 +100,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.True(razorPagesOptions.AllowAreas);
|
||||
Assert.True(mvcOptions.EnableEndpointRouting);
|
||||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||
Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -109,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
var mvcOptions = services.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||
|
|
@ -119,6 +129,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
|||
Assert.True(razorPagesOptions.AllowAreas);
|
||||
Assert.True(mvcOptions.EnableEndpointRouting);
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BasicWebSite.Models;
|
||||
|
|
@ -88,6 +85,12 @@ namespace BasicWebSite
|
|||
return foo;
|
||||
}
|
||||
|
||||
[HttpGet("[action]")]
|
||||
public ActionResult<int> ActionReturningStatusCodeResult()
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
private class TestModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -33,22 +32,6 @@ namespace BasicWebSite
|
|||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||
.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.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.
|
||||
// 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.Routing;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue