Remove many `CompatibilitySwitch<T>` properties (#4628)

- #7156 2 of 3
- will leave the `IEnumerable<ICompatibilitySwitch>` implementations to avoid `breakingchanges.netcore.json` churn
- will leave one `ConfigureCompatibilityOptions<MvcOptions>` subclass: `MvcOptionsConfigureCompatibilityOptions`
- a few options remain as regular properties:
  - `ApiBehaviorOptions.SuppressMapClientErrors` (default `false`)
  - `MvcOptions.EnableEndpointRouting` (default `true`)
  - `MvcOptions.MaxValidationDepth` (default `32`)
  - `MvcJsonOptions.AllowInputFormatterExceptionMessages` (default `true`)

nits:
- move `IsEffectivePolicy(...)` check earlier in `AuthorizeFilter`
- correct a typo or two
This commit is contained in:
Doug Bunting 2018-12-16 19:33:27 -08:00 committed by GitHub
parent 826f950530
commit 708dc5cb5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 244 additions and 1147 deletions

View File

@ -24,7 +24,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
public class DefaultApiDescriptionProvider : IApiDescriptionProvider
{
private readonly MvcOptions _mvcOptions;
private readonly IActionResultTypeMapper _mapper;
private readonly ApiResponseTypeProvider _responseTypeProvider;
private readonly RouteOptions _routeOptions;
private readonly IInlineConstraintResolver _constraintResolver;
@ -53,7 +52,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
/// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/> used for resolving inline
/// constraints.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="mapper"> The <see cref="IActionResultTypeMapper"/>.</param>
/// <param name="mapper">The <see cref="IActionResultTypeMapper"/>.</param>
/// <remarks>The <paramref name="mapper"/> parameter is currently unused.</remarks>
[Obsolete("This constructor is obsolete and will be removed in a future release.")]
public DefaultApiDescriptionProvider(
IOptions<MvcOptions> optionsAccessor,
@ -64,7 +64,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
_mvcOptions = optionsAccessor.Value;
_constraintResolver = constraintResolver;
_modelMetadataProvider = modelMetadataProvider;
_mapper = mapper;
_responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions);
}
@ -75,8 +74,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
/// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/> used for resolving inline
/// constraints.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="mapper"> The <see cref="IActionResultTypeMapper"/>.</param>
/// <param name="mapper">The <see cref="IActionResultTypeMapper"/>.</param>
/// <param name="routeOptions">The accessor for <see cref="RouteOptions"/>.</param>
/// <remarks>The <paramref name="mapper"/> parameter is currently unused.</remarks>
public DefaultApiDescriptionProvider(
IOptions<MvcOptions> optionsAccessor,
IInlineConstraintResolver constraintResolver,
@ -87,7 +87,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
_mvcOptions = optionsAccessor.Value;
_constraintResolver = constraintResolver;
_modelMetadataProvider = modelMetadataProvider;
_mapper = mapper;
_responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions);
_routeOptions = routeOptions.Value;
}
@ -202,8 +201,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
ModelMetadata metadata;
if (_mvcOptions.AllowValidatingTopLevelNodes &&
actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
if (actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
_modelMetadataProvider is ModelMetadataProvider provider)
{
// The default model metadata provider derives from ModelMetadataProvider

View File

@ -15,29 +15,9 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class ApiBehaviorOptions : IEnumerable<ICompatibilitySwitch>
{
private readonly CompatibilitySwitch<bool> _suppressMapClientErrors;
private readonly CompatibilitySwitch<bool> _suppressUseValidationProblemDetailsForInvalidModelStateResponses;
private readonly CompatibilitySwitch<bool> _allowInferringBindingSourceForCollectionTypesAsFromQuery;
private readonly ICompatibilitySwitch[] _switches;
private readonly ICompatibilitySwitch[] _switches = Array.Empty<ICompatibilitySwitch>();
private Func<ActionContext, IActionResult> _invalidModelStateResponseFactory;
/// <summary>
/// Creates a new instance of <see cref="ApiBehaviorOptions"/>.
/// </summary>
public ApiBehaviorOptions()
{
_suppressMapClientErrors = new CompatibilitySwitch<bool>(nameof(SuppressMapClientErrors));
_suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch<bool>(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses));
_allowInferringBindingSourceForCollectionTypesAsFromQuery = new CompatibilitySwitch<bool>(nameof(AllowInferringBindingSourceForCollectionTypesAsFromQuery));
_switches = new[]
{
_suppressMapClientErrors,
_suppressUseValidationProblemDetailsForInvalidModelStateResponses,
_allowInferringBindingSourceForCollectionTypesAsFromQuery
};
}
/// <summary>
/// Delegate invoked on actions annotated with <see cref="ApiControllerAttribute"/> to convert invalid
/// <see cref="ModelStateDictionary"/> into an <see cref="IActionResult"/>
@ -92,47 +72,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool SuppressMapClientErrors
{
// Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property.
get => _suppressMapClientErrors.Value;
set => _suppressMapClientErrors.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="false"/>.
/// </value>
public bool SuppressUseValidationProblemDetailsForInvalidModelStateResponses
{
get => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value;
set => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value = value;
}
/// <summary>
/// Gets or sets a value that determines if <see cref="BindingInfo.BindingSource"/> for collection types
/// (<see cref="ModelMetadata.IsCollectionType"/>).
/// <para>
/// When <see langword="true" />, the binding source for collection types is inferred as <see cref="BindingSource.Query"/>.
/// Otherwise <see cref="BindingSource.Body"/> is inferred.
/// </para>
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool AllowInferringBindingSourceForCollectionTypesAsFromQuery
{
get => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value;
set => _allowInferringBindingSourceForCollectionTypesAsFromQuery.Value = value;
}
public bool SuppressMapClientErrors { get; set; }
/// <summary>
/// Gets a map of HTTP status codes to <see cref="ClientErrorData"/>. Configured values
@ -142,8 +82,7 @@ namespace Microsoft.AspNetCore.Mvc
/// Use of this feature can be disabled by resetting <see cref="SuppressMapClientErrors"/>.
/// </para>
/// </summary>
public IDictionary<int, ClientErrorData> ClientErrorMapping { get; } =
new Dictionary<int, ClientErrorData>();
public IDictionary<int, ClientErrorData> ClientErrorMapping { get; } = new Dictionary<int, ClientErrorData>();
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{

View File

@ -49,12 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
if (!options.SuppressInferBindingSourcesForParameters)
{
var convention = new InferParameterBindingInfoConvention(modelMetadataProvider)
{
AllowInferringBindingSourceForCollectionTypesAsFromQuery = options.AllowInferringBindingSourceForCollectionTypesAsFromQuery,
};
ActionModelConventions.Add(convention);
ActionModelConventions.Add(new InferParameterBindingInfoConvention(modelMetadataProvider));
}
}

View File

@ -454,7 +454,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
var attributes = parameterInfo.GetCustomAttributes(inherit: true);
BindingInfo bindingInfo;
if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
{
var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo);
bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// An <see cref="IActionModelConvention"/> that infers <see cref="BindingInfo.BindingSource"/> for parameters.
/// </summary>
/// <remarks>
/// The goal of this covention is to make intuitive and easy to document <see cref="BindingSource"/> inferences. The rules are:
/// The goal of this convention is to make intuitive and easy to document <see cref="BindingSource"/> inferences. The rules are:
/// <list type="number">
/// <item>A previously specified <see cref="BindingInfo.BindingSource" /> is never overwritten.</item>
/// <item>A complex type parameter (<see cref="ModelMetadata.IsComplexType"/>) is assigned <see cref="BindingSource.Body"/>.</item>
@ -31,8 +31,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
_modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider));
}
internal bool AllowInferringBindingSourceForCollectionTypesAsFromQuery { get; set; }
protected virtual bool ShouldApply(ActionModel action) => true;
public void Apply(ActionModel action)
@ -118,13 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
private bool IsComplexTypeParameter(ParameterModel parameter)
{
// No need for information from attributes on the parameter. Just use its type.
var metadata = _modelMetadataProvider
.GetMetadataForType(parameter.ParameterInfo.ParameterType);
if (AllowInferringBindingSourceForCollectionTypesAsFromQuery && metadata.IsCollectionType)
{
return false;
}
var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType);
return metadata.IsComplexType;
}

View File

@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Authorization
{
@ -23,7 +22,6 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
/// </summary>
public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory
{
private MvcOptions _mvcOptions;
private AuthorizationPolicy _effectivePolicy;
/// <summary>
@ -136,38 +134,25 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
var effectivePolicy = await ComputePolicyAsync();
var canCache = PolicyProvider == null;
if (_mvcOptions == null)
// Combine all authorize filters into single effective policy that's only run on the closest filter
var builder = new AuthorizationPolicyBuilder(effectivePolicy);
for (var i = 0; i < context.Filters.Count; i++)
{
_mvcOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
}
if (_mvcOptions.AllowCombiningAuthorizeFilters)
{
if (!context.IsEffectivePolicy(this))
if (ReferenceEquals(this, context.Filters[i]))
{
return null;
continue;
}
// Combine all authorize filters into single effective policy that's only run on the closest filter
var builder = new AuthorizationPolicyBuilder(effectivePolicy);
for (var i = 0; i < context.Filters.Count; i++)
if (context.Filters[i] is AuthorizeFilter authorizeFilter)
{
if (ReferenceEquals(this, context.Filters[i]))
{
continue;
}
if (context.Filters[i] is AuthorizeFilter authorizeFilter)
{
// Combine using the explicit policy, or the dynamic policy provider
builder.Combine(await authorizeFilter.ComputePolicyAsync());
canCache = canCache && authorizeFilter.PolicyProvider == null;
}
// Combine using the explicit policy, or the dynamic policy provider
builder.Combine(await authorizeFilter.ComputePolicyAsync());
canCache = canCache && authorizeFilter.PolicyProvider == null;
}
effectivePolicy = builder?.Build() ?? effectivePolicy;
}
effectivePolicy = builder?.Build() ?? effectivePolicy;
// We can cache the effective policy when there is no custom policy provider
if (canCache)
{
@ -185,6 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
throw new ArgumentNullException(nameof(context));
}
if (!context.IsEffectivePolicy(this))
{
return;
}
var effectivePolicy = await GetEffectivePolicyAsync(context);
if (effectivePolicy == null)
{

View File

@ -54,8 +54,6 @@ namespace Microsoft.AspNetCore.Mvc
/// <remarks>
/// ASP.NET Core MVC 2.1 introduced compatibility switches for the following:
/// <list type="bullet">
/// <item><description><see cref="MvcOptions.AllowBindingHeaderValuesToNonStringModelTypes"/></description></item>
/// <item><description><see cref="MvcOptions.InputFormatterExceptionPolicy"/></description></item>
/// <item><description><see cref="MvcOptions.InputFormatterExceptionPolicy"/></description></item>
/// <item><description><see cref="MvcOptions.SuppressBindingUndefinedValueToEnumType"/></description></item>
/// <item><description><c>MvcJsonOptions.AllowInputFormatterExceptionMessages</c></description></item>
@ -75,7 +73,6 @@ namespace Microsoft.AspNetCore.Mvc
/// ASP.NET Core MVC 2.2 introduced compatibility switches for the following:
/// <list type="bullet">
/// <item><description><c>ApiBehaviorOptions.SuppressMapClientErrors</c></description></item>
/// <item><description><c>ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses</c></description></item>
/// <item><description><c>MvcDataAnnotationsLocalizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes</c></description></item>
/// <item><description><see cref="MvcOptions.EnableEndpointRouting" /></description></item>
/// <item><description><see cref="MvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent"/></description></item>

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.Controllers
@ -134,8 +133,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
var parameter = parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider

View File

@ -2,37 +2,21 @@
// 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.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
internal class ApiBehaviorOptionsSetup :
ConfigureCompatibilityOptions<ApiBehaviorOptions>,
IConfigureOptions<ApiBehaviorOptions>
IConfigureOptions<ApiBehaviorOptions>,
IPostConfigureOptions<ApiBehaviorOptions>
{
internal static readonly Func<ActionContext, IActionResult> DefaultFactory = DefaultInvalidModelStateResponse;
internal static readonly Func<ActionContext, IActionResult> ProblemDetailsFactory = ProblemDetailsInvalidModelStateResponse;
public ApiBehaviorOptionsSetup(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
return new Dictionary<string, object>();
}
}
internal static readonly Func<ActionContext, IActionResult> ProblemDetailsFactory =
ProblemDetailsInvalidModelStateResponse;
public void Configure(ApiBehaviorOptions options)
{
@ -45,11 +29,8 @@ namespace Microsoft.Extensions.DependencyInjection
ConfigureClientErrorMapping(options);
}
public override void PostConfigure(string name, ApiBehaviorOptions options)
public 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 (SuppressMapClientErrors = true)
// (b) a different factory was configured

View File

@ -30,18 +30,18 @@ namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Adds the minimum essential MVC services to the specified <see cref="IServiceCollection" />. Additional services
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// <see cref="IMvcCoreBuilder"/> returned from this method.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>An <see cref="IMvcCoreBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// The <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> approach for configuring
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// registered. <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> will register
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// application will satisfy its requirements with just a call to
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="IMvcCoreBuilder"/> will be required.
/// </remarks>
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
@ -100,7 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Adds the minimum essential MVC services to the specified <see cref="IServiceCollection" />. Additional services
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// <see cref="IMvcCoreBuilder"/> returned from this method.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
@ -108,11 +108,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns>An <see cref="IMvcCoreBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// The <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> approach for configuring
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// registered. <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> will register
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// application will satisfy its requirements with just a call to
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="IMvcCoreBuilder"/> will be required.
/// </remarks>
public static IMvcCoreBuilder AddMvcCore(

View File

@ -49,20 +49,22 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor)
{
var controllerContext = new ControllerContext(context.ActionContext);
// PERF: These are rarely going to be changed, so let's go copy-on-write.
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories);
var controllerContext = new ControllerContext(context.ActionContext)
{
// PERF: These are rarely going to be changed, so let's go copy-on-write.
ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories)
};
controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext);
var (cacheEntry, filters) = _controllerActionInvokerCache.GetCachedResult(controllerContext);
var invoker = new ControllerActionInvoker(
_logger,
_diagnosticListener,
_mapper,
controllerContext,
cacheResult.cacheEntry,
cacheResult.filters);
cacheEntry,
filters);
context.Result = invoker;
}

View File

@ -23,16 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
return new Dictionary<string, object>
{
[nameof(MvcOptions.AllowCombiningAuthorizeFilters)] = true,
[nameof(MvcOptions.AllowBindingHeaderValuesToNonStringModelTypes)] = true,
[nameof(MvcOptions.AllowValidatingTopLevelNodes)] = true,
[nameof(MvcOptions.InputFormatterExceptionPolicy)] = InputFormatterExceptionPolicy.MalformedInputExceptions,
[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true,
[nameof(MvcOptions.EnableEndpointRouting)] = true,
// Matches JsonSerializerSettingsProvider.DefaultMaxDepth
[nameof(MvcOptions.MaxValidationDepth)] = 32,
[nameof(MvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent)] = true,
};
}

View File

@ -38,10 +38,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// The <see cref="IModelBinder"/> for binding <typeparamref name="TElement"/>.
/// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public ArrayModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory)
: base(elementBinder, loggerFactory)
{
@ -59,11 +55,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
/// <remarks>
/// The <paramref name="allowValidatingTopLevelNodes"/> parameter is currently ignored.
/// <see cref="CollectionModelBinder{TElement}.AllowValidatingTopLevelNodes"/> is always <see langword="true"/>
/// in <see cref="ArrayModelBinder{TElement}"/>.
/// </remarks>
public ArrayModelBinder(
IModelBinder elementBinder,
ILoggerFactory loggerFactory,
bool allowValidatingTopLevelNodes)
: base(elementBinder, loggerFactory, allowValidatingTopLevelNodes)
: base(elementBinder, loggerFactory, allowValidatingTopLevelNodes: true)
{
}

View File

@ -4,7 +4,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
@ -28,12 +27,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return (IModelBinder)Activator.CreateInstance(
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
true /* allowValidatingTopLevelNodes */);
}
return null;

View File

@ -42,12 +42,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary>
/// <param name="elementBinder">The <see cref="IModelBinder"/> for binding elements.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public CollectionModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory)
: this(elementBinder, loggerFactory, allowValidatingTopLevelNodes: false)
: this(elementBinder, loggerFactory, allowValidatingTopLevelNodes: true)
{
}
@ -196,16 +192,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// </summary>
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
/// <remarks>
/// <para>
/// This method should be called only when <see cref="MvcOptions.AllowValidatingTopLevelNodes" /> is
/// <see langword="true" /> and a top-level model was not bound.
/// </para>
/// <para>
/// For back-compatibility reasons, <see cref="ModelBindingContext.Result" /> must have
/// <see cref="ModelBindingResult.IsModelSet" /> equal to <see langword="true" /> when a
/// top-level model is not bound. Therefore, ParameterBinder can not detect a
/// <see cref="ModelMetadata.IsBindingRequired" /> failure for collections. Add the error here.
/// </para>
/// </remarks>
protected void AddErrorIfBindingRequired(ModelBindingContext bindingContext)
{

View File

@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
true /* allowValidatingTopLevelNodes */);
}
// If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since
@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
binderType,
elementBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
true /* allowValidatingTopLevelNodes */);
}
}

View File

@ -44,14 +44,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// The <see cref="IDictionary{TKey, TValue}"/> of binders to use for binding properties.
/// </param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public ComplexTypeModelBinder(
IDictionary<ModelMetadata, IModelBinder> propertyBinders,
ILoggerFactory loggerFactory)
: this(propertyBinders, loggerFactory, allowValidatingTopLevelNodes: false)
: this(propertyBinders, loggerFactory, allowValidatingTopLevelNodes: true)
{
}
@ -67,6 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
/// <remarks>The <paramref name="allowValidatingTopLevelNodes"/> parameter is currently ignored.</remarks>
public ComplexTypeModelBinder(
IDictionary<ModelMetadata, IModelBinder> propertyBinders,
ILoggerFactory loggerFactory,
@ -84,13 +81,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
_propertyBinders = propertyBinders;
_logger = loggerFactory.CreateLogger<ComplexTypeModelBinder>();
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
}
// Internal for testing.
internal bool AllowValidatingTopLevelNodes { get; }
/// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
@ -172,8 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// 1. The top-level model has no public settable properties.
// 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding.
// 3. No data exists for any property.
if (AllowValidatingTopLevelNodes &&
!attemptedPropertyBinding &&
if (!attemptedPropertyBinding &&
bindingContext.IsTopLevelObject &&
modelMetadata.IsBindingRequired)
{

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
@ -32,11 +31,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
return new ComplexTypeModelBinder(
propertyBinders,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
allowValidatingTopLevelNodes: true);
}
return null;

View File

@ -41,10 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <param name="keyBinder">The <see cref="IModelBinder"/> for <typeparamref name="TKey"/>.</param>
/// <param name="valueBinder">The <see cref="IModelBinder"/> for <typeparamref name="TValue"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>
/// The binder will not add an error for an unbound top-level model even if
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/>.
/// </remarks>
public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory)
: base(new KeyValuePairModelBinder<TKey, TValue>(keyBinder, valueBinder, loggerFactory), loggerFactory)
{
@ -67,6 +63,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <see cref="ModelMetadata.IsBindingRequired"/> is <see langword="true"/> for a top-level model, the binder
/// adds a <see cref="ModelStateDictionary"/> error when the model is not bound.
/// </param>
/// <remarks>
/// The <paramref name="allowValidatingTopLevelNodes"/> parameter is currently ignored.
/// <see cref="CollectionModelBinder{TElement}.AllowValidatingTopLevelNodes"/> is always
/// <see langword="false"/> in <see cref="DictionaryModelBinder{TKey, TValue}"/>. This class ignores that
/// property and unconditionally checks for unbound top-level models with
/// <see cref="ModelMetadata.IsBindingRequired"/>.
/// </remarks>
public DictionaryModelBinder(
IModelBinder keyBinder,
IModelBinder valueBinder,
@ -84,12 +87,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
_valueBinder = valueBinder;
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
}
// Internal for testing.
internal new bool AllowValidatingTopLevelNodes { get; }
/// <inheritdoc />
public override async Task BindModelAsync(ModelBindingContext bindingContext)
{
@ -121,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
// No IEnumerableValueProvider available for the fallback approach. For example the user may have
// replaced the ValueProvider with something other than a CompositeValueProvider.
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
if (bindingContext.IsTopLevelObject)
{
AddErrorIfBindingRequired(bindingContext);
}
@ -135,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
if (keys.Count == 0)
{
// No entries with the expected keys.
if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject)
if (bindingContext.IsTopLevelObject)
{
AddErrorIfBindingRequired(bindingContext);
}

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
keyBinder,
valueBinder,
loggerFactory,
mvcOptions.AllowValidatingTopLevelNodes);
true /* allowValidatingTopLevelNodes */);
}
return null;

View File

@ -4,7 +4,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
@ -32,23 +31,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<HeaderModelBinderProvider>();
var options = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
if (!options.AllowBindingHeaderValuesToNonStringModelTypes)
{
if (modelMetadata.ModelType == typeof(string) ||
modelMetadata.ElementType == typeof(string))
{
return new HeaderModelBinder(loggerFactory);
}
else
{
logger.CannotCreateHeaderModelBinderCompatVersion_2_0(modelMetadata.ModelType);
}
return null;
}
if (!IsSimpleType(modelMetadata))
{
logger.CannotCreateHeaderModelBinder(modelMetadata.ModelType);

View File

@ -466,7 +466,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
if (defaultModelMetadata.ValidationMetadata.HasValidators != false)
{
// Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null).
// Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null).
// In either case, assume it has validators.
return true;
}

View File

@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly MvcOptions _mvcOptions;
private readonly IObjectModelValidator _objectModelValidator;
/// <summary>
@ -55,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
/// <param name="mvcOptions">The <see cref="MvcOptions"/> accessor.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
/// <remarks>The <paramref name="mvcOptions"/> parameter is currently unused.</remarks>
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
@ -90,7 +90,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
_modelMetadataProvider = modelMetadataProvider;
_modelBinderFactory = modelBinderFactory;
_objectModelValidator = validator;
_mvcOptions = mvcOptions.Value;
Logger = loggerFactory.CreateLogger(GetType());
}
@ -260,8 +259,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var modelBindingResult = modelBindingContext.Result;
if (_mvcOptions.AllowValidatingTopLevelNodes &&
_objectModelValidator is ObjectModelValidator baseObjectValidator)
if (_objectModelValidator is ObjectModelValidator baseObjectValidator)
{
Logger.AttemptingToValidateParameterOrProperty(parameter, metadata);

View File

@ -39,10 +39,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
validatorProvider,
validatorCache,
metadataProvider,
validationState);
visitor.MaxValidationDepth = _mvcOptions.MaxValidationDepth;
visitor.AllowShortCircuitingValidationWhenNoValidatorsArePresent = _mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent;
validationState)
{
MaxValidationDepth = _mvcOptions.MaxValidationDepth,
AllowShortCircuitingValidationWhenNoValidatorsArePresent =
_mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent,
};
return visitor;
}

View File

@ -4,14 +4,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -22,18 +19,14 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class MvcOptions : IEnumerable<ICompatibilitySwitch>
{
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
// See CompatibilitySwitch.cs for guide on how to implement these.
private readonly CompatibilitySwitch<bool> _allowBindingHeaderValuesToNonStringModelTypes;
private readonly CompatibilitySwitch<bool> _allowCombiningAuthorizeFilters;
private readonly CompatibilitySwitch<bool> _allowValidatingTopLevelNodes;
private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly CompatibilitySwitch<bool> _enableEndpointRouting;
private readonly NullableCompatibilitySwitch<int> _maxValidationDepth;
private readonly CompatibilitySwitch<bool> _allowShortCircuitingValidationWhenNoValidatorsArePresent;
private readonly ICompatibilitySwitch[] _switches;
private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors;
private int? _maxValidationDepth = 32;
/// <summary>
/// Creates a new instance of <see cref="MvcOptions"/>.
@ -52,24 +45,14 @@ namespace Microsoft.AspNetCore.Mvc
ModelValidatorProviders = new List<IModelValidatorProvider>();
ValueProviderFactories = new List<IValueProviderFactory>();
_allowCombiningAuthorizeFilters = new CompatibilitySwitch<bool>(nameof(AllowCombiningAuthorizeFilters));
_allowBindingHeaderValuesToNonStringModelTypes = new CompatibilitySwitch<bool>(nameof(AllowBindingHeaderValuesToNonStringModelTypes));
_allowValidatingTopLevelNodes = new CompatibilitySwitch<bool>(nameof(AllowValidatingTopLevelNodes));
_inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_enableEndpointRouting = new CompatibilitySwitch<bool>(nameof(EnableEndpointRouting));
_maxValidationDepth = new NullableCompatibilitySwitch<int>(nameof(MaxValidationDepth));
_allowShortCircuitingValidationWhenNoValidatorsArePresent = new CompatibilitySwitch<bool>(nameof(AllowShortCircuitingValidationWhenNoValidatorsArePresent));
_switches = new ICompatibilitySwitch[]
{
_allowCombiningAuthorizeFilters,
_allowBindingHeaderValuesToNonStringModelTypes,
_allowValidatingTopLevelNodes,
_inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType,
_enableEndpointRouting,
_maxValidationDepth,
_allowShortCircuitingValidationWhenNoValidatorsArePresent,
};
}
@ -82,11 +65,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <value>
/// The default value is <see langword="true"/>.
/// </value>
public bool EnableEndpointRouting
{
get => _enableEndpointRouting.Value;
set => _enableEndpointRouting.Value = value;
}
public bool EnableEndpointRouting { get; set; } = true;
/// <summary>
/// Gets or sets the flag which decides whether body model binding (for example, on an
@ -100,58 +79,6 @@ namespace Microsoft.AspNetCore.Mvc
/// </example>
public bool AllowEmptyInputInBodyModelBinding { get; set; }
/// <summary>
/// Gets or sets a value that determines if policies on instances of <see cref="AuthorizeFilter" />
/// will be combined into a single effective policy.
/// </summary>
/// <value>
/// The default value is <see langword="true"/>.
/// </value>
/// <remarks>
/// Authorization policies are designed such that multiple authorization policies applied to an endpoint
/// should be combined and executed a single policy. The <see cref="AuthorizeFilter"/> (commonly applied
/// by <see cref="AuthorizeAttribute"/>) can be applied globally, to controllers, and to actions - which
/// specifies multiple authorization policies for an action. In all ASP.NET Core releases prior to 2.1
/// these multiple policies would not combine as intended. This compatibility switch configures whether the
/// old (unintended) behavior or the new combining behavior will be used when multiple authorization policies
/// are applied.
/// </remarks>
public bool AllowCombiningAuthorizeFilters
{
get => _allowCombiningAuthorizeFilters.Value;
set => _allowCombiningAuthorizeFilters.Value = value;
}
/// <summary>
/// Gets or sets a value that determines if <see cref="HeaderModelBinder"/> should bind to types other than
/// <see cref="string"/> or a collection of <see cref="string"/>. If set to <c>true</c>,
/// <see cref="HeaderModelBinder"/> would bind to simple types (like <see cref="string"/>, <see cref="int"/>,
/// <see cref="Enum"/>, <see cref="bool"/> etc.) or a collection of simple types.
/// </summary>
/// <value>
/// The default value is <see langword="true"/>.
/// </value>
public bool AllowBindingHeaderValuesToNonStringModelTypes
{
get => _allowBindingHeaderValuesToNonStringModelTypes.Value;
set => _allowBindingHeaderValuesToNonStringModelTypes.Value = value;
}
/// <summary>
/// Gets or sets a value that determines if model bound action parameters, controller properties, page handler
/// parameters, or page model properties are validated (in addition to validating their elements or
/// properties). If set to <see langword="true"/>, <see cref="BindRequiredAttribute"/> and
/// <c>ValidationAttribute</c>s on these top-level nodes are checked. Otherwise, such attributes are ignored.
/// </summary>
/// <value>
/// The default value is <see langword="true"/>.
/// </value>
public bool AllowValidatingTopLevelNodes
{
get => _allowValidatingTopLevelNodes.Value;
set => _allowValidatingTopLevelNodes.Value = value;
}
/// <summary>
/// Gets a Dictionary of CacheProfile Names, <see cref="CacheProfile"/> which are pre-defined settings for
/// response caching.
@ -311,7 +238,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </value>
public int? MaxValidationDepth
{
get => _maxValidationDepth.Value;
get => _maxValidationDepth;
set
{
if (value != null && value <= 0)
@ -319,7 +246,7 @@ namespace Microsoft.AspNetCore.Mvc
throw new ArgumentOutOfRangeException(nameof(value));
}
_maxValidationDepth.Value = value;
_maxValidationDepth = value;
}
}

View File

@ -72,5 +72,35 @@
"TypeId": "public abstract class Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator : Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator",
"MemberId": "public abstract Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor GetValidationVisitor(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider validatorProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidatorCache validatorCache, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider metadataProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary validationState)",
"Kind": "Addition"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Boolean get_AllowBindingHeaderValuesToNonStringModelTypes()",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Boolean get_AllowCombiningAuthorizeFilters()",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Boolean get_AllowValidatingTopLevelNodes()",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Void set_AllowBindingHeaderValuesToNonStringModelTypes(System.Boolean value)",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Void set_AllowCombiningAuthorizeFilters(System.Boolean value)",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Mvc.MvcOptions : System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch>",
"MemberId": "public System.Void set_AllowValidatingTopLevelNodes(System.Boolean value)",
"Kind": "Removal"
}
]
]

View File

@ -75,8 +75,6 @@ namespace Microsoft.Extensions.DependencyInjection
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcJsonMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcJsonOptions>, MvcJsonOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, JsonPatchOperationsArrayProvider>());
services.TryAddSingleton<JsonResultExecutor>();

View File

@ -1,32 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
internal class MvcJsonOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions<MvcJsonOptions>
{
public MvcJsonOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
return new Dictionary<string, object>
{
[nameof(MvcJsonOptions.AllowInputFormatterExceptionMessages)] = true,
};
}
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Formatters;
@ -15,21 +16,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
public class MvcJsonOptions : IEnumerable<ICompatibilitySwitch>
{
private readonly CompatibilitySwitch<bool> _allowInputFormatterExceptionMessages;
private readonly ICompatibilitySwitch[] _switches;
/// <summary>
/// Creates a new instance of <see cref="MvcJsonOptions"/>.
/// </summary>
public MvcJsonOptions()
{
_allowInputFormatterExceptionMessages = new CompatibilitySwitch<bool>(nameof(AllowInputFormatterExceptionMessages));
_switches = new ICompatibilitySwitch[]
{
_allowInputFormatterExceptionMessages,
};
}
private readonly ICompatibilitySwitch[] _switches = Array.Empty<ICompatibilitySwitch>();
/// <summary>
/// Gets or sets a flag to determine whether error messages from JSON deserialization by the
@ -44,11 +31,7 @@ namespace Microsoft.AspNetCore.Mvc
/// or using <see cref="BadRequestObjectResult"/>. In effect, this setting controls whether clients can receive
/// detailed error messages about submitted JSON data.
/// </remarks>
public bool AllowInputFormatterExceptionMessages
{
get => _allowInputFormatterExceptionMessages.Value;
set => _allowInputFormatterExceptionMessages.Value = value;
}
public bool AllowInputFormatterExceptionMessages { get; set; } = true;
/// <summary>
/// Gets the <see cref="JsonSerializerSettings"/> that are used by this application.

View File

@ -11,9 +11,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
/// <summary>
/// Serializes <see cref="IEnumerable{T}"/> types by delegating them through a concrete implementation.
/// </summary>
/// <typeparam name="TWrapped">The wrapping or original type of the <see cref="IEnumerable{T}"/>
/// <typeparam name="TWrapped">The wrapping or original type of the <see cref="IEnumerable{T}"/>
/// to proxy.</typeparam>
/// <typeparam name="TDeclared">The type parameter of the original <see cref="IEnumerable{T}"/>
/// <typeparam name="TDeclared">The type parameter of the original <see cref="IEnumerable{T}"/>
/// to proxy.</typeparam>
public class DelegatingEnumerable<TWrapped, TDeclared> : IEnumerable<TWrapped>
{
@ -21,10 +21,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
private readonly IWrapperProvider _wrapperProvider;
/// <summary>
/// Initializes a <see cref="DelegatingEnumerable{TWrapped, TDeclared}"/>.
/// Initializes a <see cref="DelegatingEnumerable{TWrapped, TDeclared}"/>.
/// </summary>
/// <remarks>
/// This constructor is necessary for <see cref="System.Runtime.Serialization.DataContractSerializer"/>
/// This constructor is necessary for <see cref="System.Runtime.Serialization.DataContractSerializer"/>
/// to serialize.
/// </remarks>
public DelegatingEnumerable()
@ -64,7 +64,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
/// This type will never be used for deserialization, but we are required to implement the add
/// method so that the type can be serialized. This will never be called.
/// </summary>
/// <param name="item">The item to add. Unused.</param>
/// <param name="item">The item to add.</param>
/// <exception cref="NotImplementedException">Thrown unconditionally.</exception>
public void Add(object item)
{
throw new NotImplementedException();
@ -80,4 +81,4 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
return GetEnumerator();
}
}
}
}

View File

@ -21,7 +21,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
private readonly PageHandlerPageFilter _pageHandlerPageFilter = new PageHandlerPageFilter();
private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter();
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly MvcOptions _mvcOptions;
private readonly RazorPagesOptions _razorPagesOptions;
private readonly Func<ActionContext, bool> _supportsAllRequests;
private readonly Func<ActionContext, bool> _supportsNonGetRequests;
@ -29,11 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public DefaultPageApplicationModelProvider(
IModelMetadataProvider modelMetadataProvider,
IOptions<MvcOptions> options,
IOptions<RazorPagesOptions> razorPagesOptions)
{
_modelMetadataProvider = modelMetadataProvider;
_mvcOptions = options.Value;
_razorPagesOptions = razorPagesOptions.Value;
_supportsAllRequests = _ => true;
@ -248,7 +245,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
var attributes = parameter.GetCustomAttributes(inherit: true);
BindingInfo bindingInfo;
if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
{
var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter);
bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);

View File

@ -102,8 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
var parameter = handler.Parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.

View File

@ -1180,25 +1180,6 @@ namespace Microsoft.AspNetCore.Mvc.Description
Assert.True(parameter.ModelMetadata.IsBindingRequired);
}
[Fact]
public void GetApiDescription_ParameterDescription_IsRequiredNotSet_IfNotValidatingTopLevelNodes()
{
// Arrange
var action = CreateActionDescriptor(nameof(RequiredParameter));
// Act
var descriptions = GetApiDescriptions(action, allowValidatingTopLevelNodes: false);
// Assert
var description = Assert.Single(descriptions);
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("name", parameter.Name);
Assert.Same(BindingSource.ModelBinding, parameter.Source);
Assert.Equal(typeof(string), parameter.Type);
Assert.False(parameter.ModelMetadata.IsRequired);
Assert.False(parameter.ModelMetadata.IsBindingRequired);
}
[Fact]
public void GetApiDescription_ParameterDescription_SourceFromRouteData()
{
@ -1816,15 +1797,11 @@ namespace Microsoft.AspNetCore.Mvc.Description
ActionDescriptor action,
List<MockInputFormatter> inputFormatters = null,
List<MockOutputFormatter> outputFormatters = null,
bool allowValidatingTopLevelNodes = true,
RouteOptions routeOptions = null)
{
var context = new ApiDescriptionProviderContext(new ActionDescriptor[] { action });
var options = new MvcOptions
{
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes,
};
var options = new MvcOptions();
foreach (var formatter in inputFormatters ?? CreateInputFormatters())
{
options.InputFormatters.Add(formatter);

View File

@ -169,19 +169,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
loggerFactory);
}
private static ApplicationModelProviderContext GetContext(
Type type,
IModelMetadataProvider modelMetadataProvider = null)
{
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true });
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
var provider = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider);
provider.OnProvidersExecuting(context);
return context;
}
private class TestApiController : ControllerBase
{
public IActionResult TestAction(object value) => null;

View File

@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
IModelMetadataProvider modelMetadataProvider = null)
{
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true });
var mvcOptions = Options.Create(new MvcOptions());
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider);
convention.OnProvidersExecuting(context);
@ -68,12 +68,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
return context;
}
private static ControllerModel GetControllerModel(Type controllerType)
{
var context = GetContext(controllerType);
return Assert.Single(context.Result.Controllers);
}
private static ActionModel GetActionModel(Type controllerType, string actionName)
{
var context = GetContext(controllerType);

View File

@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
// Arrange
var builder = new TestApplicationModelProvider(
new MvcOptions { AllowValidatingTopLevelNodes = true },
new MvcOptions(),
TestModelMetadataProvider.CreateDefaultProvider());
var typeInfo = typeof(ModelBinderController).GetTypeInfo();
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
// Arrange
var builder = new TestApplicationModelProvider(
new MvcOptions { AllowValidatingTopLevelNodes = true },
new MvcOptions(),
TestModelMetadataProvider.CreateDefaultProvider());
var typeInfo = typeof(ModelBinderController).GetTypeInfo();
@ -191,57 +191,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
});
}
[Fact]
public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_WithLegacyValidationBehavior()
{
// Arrange
var builder = new TestApplicationModelProvider(
new MvcOptions(),
TestModelMetadataProvider.CreateDefaultProvider());
var typeInfo = typeof(ModelBinderController).GetTypeInfo();
var context = new ApplicationModelProviderContext(new[] { typeInfo });
// Act
builder.OnProvidersExecuting(context);
// Assert
var controllerModel = Assert.Single(context.Result.Controllers);
var action = Assert.Single(controllerModel.Actions, a => a.ActionMethod.Name == nameof(ModelBinderController.PostAction));
Assert.Collection(
action.Parameters,
parameter =>
{
Assert.Equal("fromQuery", parameter.ParameterName);
Assert.Equal(BindingSource.Query, parameter.BindingInfo.BindingSource);
Assert.Same(action, parameter.Action);
var attribute = Assert.Single(parameter.Attributes);
Assert.IsType<FromQueryAttribute>(attribute);
},
parameter =>
{
Assert.Equal("formFileCollection", parameter.ParameterName);
// BindingSource for IFormFileCollection comes from ModelMetadata which we are not using here.
Assert.Null(parameter.BindingInfo);
Assert.Same(action, parameter.Action);
Assert.Empty(parameter.Attributes);
},
parameter =>
{
Assert.Equal("unbound", parameter.ParameterName);
Assert.Null(parameter.BindingInfo);
Assert.Same(action, parameter.Action);
});
}
[Fact]
public void OnProvidersExecuting_InfersFormFileSourceForTypesAssignableFromIEnumerableOfFormFiles()
{
// Arrange
var builder = new TestApplicationModelProvider(
new MvcOptions { AllowValidatingTopLevelNodes = true },
new MvcOptions(),
TestModelMetadataProvider.CreateDefaultProvider());
var typeInfo = typeof(ModelBinderController).GetTypeInfo();
@ -286,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_ReadFromModelMetadata()
{
// Arrange
var options = new MvcOptions { AllowValidatingTopLevelNodes = true };
var options = new MvcOptions();
var detailsProvider = new BindingSourceMetadataProvider(typeof(Guid), BindingSource.Special);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider });
@ -1822,9 +1777,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
private class TestApplicationModelProvider : DefaultApplicationModelProvider
{
public TestApplicationModelProvider()
: this(
new MvcOptions { AllowValidatingTopLevelNodes = true },
new EmptyModelMetadataProvider())
: this(new MvcOptions(), new EmptyModelMetadataProvider())
{
}
@ -1836,4 +1789,4 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
}
}
}
}
}

View File

@ -468,22 +468,6 @@ Environment.NewLine + "int b";
Assert.Same(BindingSource.Body, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsQueryForCollectionOfSimpleTypes_WhenAllowInferringBindingSourceForCollectionTypesAsFromQueryIsSet()
{
// Arrange
var actionName = nameof(ParameterBindingController.CollectionOfSimpleTypes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
convention.AllowInferringBindingSourceForCollectionTypesAsFromQuery = true;
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Query, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsBodyForCollectionOfComplexTypes()
{
@ -499,22 +483,6 @@ Environment.NewLine + "int b";
Assert.Same(BindingSource.Body, result);
}
[Fact]
public void InferBindingSourceForParameter_ReturnsQueryForCollectionOfComplexTypes_WhenAllowInferringBindingSourceForCollectionTypesAsFromQueryIsSet()
{
// Arrange
var actionName = nameof(ParameterBindingController.CollectionOfComplexTypes);
var parameter = GetParameterModel(typeof(ParameterBindingController), actionName);
var convention = GetConvention();
convention.AllowInferringBindingSourceForCollectionTypesAsFromQuery = true;
// Act
var result = convention.InferBindingSourceForParameter(parameter);
// Assert
Assert.Same(BindingSource.Query, result);
}
[Fact]
public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName()
{
@ -782,7 +750,7 @@ Environment.NewLine + "int b";
IModelMetadataProvider modelMetadataProvider = null)
{
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true });
var mvcOptions = Options.Create(new MvcOptions());
modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider);
convention.OnProvidersExecuting(context);

View File

@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
var authorizationContext = GetAuthorizationContext(anonymous: true);
// The type 'AuthorizeFilter' is both a filter by itself and also a filter factory.
// The default filter provider first checks if a type is a filter factory and creates an instance of
// this filter.
@ -38,6 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
var filterFactory = authorizeFilterFactory as IFilterFactory;
var authorizeFilter = (AuthorizeFilter)filterFactory.CreateInstance(
authorizationContext.HttpContext.RequestServices);
authorizationContext.Filters.Add(authorizeFilter);
// Act
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
@ -52,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Arrange
var authorizeFilter = new AuthorizeFilter(new[] { new AuthorizeAttribute() });
var authorizationContext = GetAuthorizationContext();
authorizationContext.Filters.Add(authorizeFilter);
var expected = "An AuthorizationPolicy cannot be created without a valid instance of " +
"IAuthorizationPolicyProvider.";
@ -67,6 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Arrange
var authorizeFilter = new AuthorizeFilter(new[] { new AuthorizeAttribute() });
var authorizationContext = GetAuthorizationContext();
authorizationContext.Filters.Add(authorizeFilter);
var expected = "An AuthorizationPolicy cannot be created without a valid instance of " +
"IAuthorizationPolicyProvider.";
@ -101,6 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
.Callback(() => getPolicyCount++);
var authorizeFilter = new AuthorizeFilter(policyProvider.Object, new AuthorizeAttribute[] { new AuthorizeAttribute("whatever") });
var authorizationContext = GetAuthorizationContext();
authorizationContext.Filters.Add(authorizeFilter);
// Act & Assert
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
@ -153,6 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
var authorizationContext = GetAuthorizationContext(anonymous: true);
authorizationContext.Filters.Add(authorizeFilter);
// Act
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
@ -223,8 +229,10 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
private class TestPolicyProvider : IAuthorizationPolicyProvider
{
private AuthorizationPolicy _true = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
private AuthorizationPolicy _false = new AuthorizationPolicyBuilder().RequireAssertion(_ => false).Build();
private readonly AuthorizationPolicy _true =
new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
private readonly AuthorizationPolicy _false =
new AuthorizationPolicyBuilder().RequireAssertion(_ => false).Build();
public int GetPolicyCalls = 0;
@ -250,7 +258,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
new AuthorizeAttribute { Policy = "true"},
new AuthorizeAttribute { Policy = "false"}
});
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
var authorizationContext = GetAuthorizationContext(anonymous: false);
// Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
@ -269,12 +277,12 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Arrange
var testProvider1 = new TestPolicyProvider();
var testProvider2 = new TestPolicyProvider();
var authorizeFilter = new AuthorizeFilter(testProvider1, new IAuthorizeData[]
var authorizeFilter = new AuthorizeFilter(testProvider1, new IAuthorizeData[]
{
new AuthorizeAttribute { Policy = "true"},
new AuthorizeAttribute { Policy = "false"}
});
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
var authorizationContext = GetAuthorizationContext(anonymous: false);
// Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
@ -304,7 +312,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
var authorizationContext = GetAuthorizationContext(anonymous: false);
// Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -322,7 +330,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
var authorizationContext = GetAuthorizationContext(anonymous: false);
// Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter);
var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build());
@ -340,7 +348,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
{
// Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build());
var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure<MvcOptions>(o => o.AllowCombiningAuthorizeFilters = true));
var authorizationContext = GetAuthorizationContext(anonymous: false);
// Effective policy should fail, if both are combined
authorizationContext.Filters.Add(authorizeFilter);
authorizationContext.Filters.Add(new DerivedAuthorizeFilter());
@ -354,8 +362,8 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Assert
Assert.IsType<ForbidResult>(authorizationContext.Result);
}
public class DerivedAuthorizeFilter : AuthorizeFilter
public class DerivedAuthorizeFilter : AuthorizeFilter
{
public DerivedAuthorizeFilter() : base(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build())
{ }
@ -381,6 +389,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
// Arrange
var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("Wut").Build());
var authorizationContext = GetAuthorizationContext();
authorizationContext.Filters.Add(authorizeFilter);
// Act
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
@ -397,6 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
.RequireClaim("Permission", "CanViewComment")
.Build());
var authorizationContext = GetAuthorizationContext();
authorizationContext.Filters.Add(authorizeFilter);
// Act
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
@ -532,10 +542,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
serviceCollection.AddSingleton(auth.Object);
serviceCollection.AddAuthorization();
serviceCollection.AddAuthorizationPolicyEvaluator();
if (registerServices != null)
{
registerServices(serviceCollection);
}
registerServices?.Invoke(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
@ -557,7 +564,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
routeData: new RouteData(),
actionDescriptor: new ActionDescriptor());
var authorizationContext = new Filters.AuthorizationFilterContext(
var authorizationContext = new AuthorizationFilterContext(
actionContext,
Enumerable.Empty<IFilterMetadata>().ToList()
);

View File

@ -9,7 +9,6 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -28,10 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
{
public class ControllerBinderDelegateProviderTest
{
private static readonly MvcOptions _options = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
private static readonly MvcOptions _options = new MvcOptions();
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(_options);
[Fact]
@ -428,55 +424,6 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
controllerContext.ModelState["memberName"].Errors.Single().ErrorMessage);
}
[Fact]
public async Task CreateBinderDelegate_Delegate_DoesNotCallValidator_IfNotValidatingTopLevelNodes()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
actionDescriptor.Parameters.Add(
new ControllerParameterDescriptor
{
Name = "foo",
ParameterType = typeof(object),
ParameterInfo = ParameterInfos.CustomValidationParameterInfo
});
var controllerContext = GetControllerContext(actionDescriptor);
var factory = GetModelBinderFactory("Hello");
var mockValidator = new Mock<IModelValidator>();
mockValidator
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
.Returns(new[] { new ModelValidationResult("memberName", "some message") });
// Do not set AllowValidatingTopLevelNodes.
var mvcOptions = new MvcOptions();
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)),
Options.Create(mvcOptions),
NullLoggerFactory.Instance);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor,
mvcOptions);
await binderDelegate(controllerContext, controller, arguments);
// Assert
Assert.True(controllerContext.ModelState.IsValid);
mockValidator.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Never());
}
[Fact]
public async Task CreateBinderDelegate_Delegate_DoesNotCallValidator_IfModelBinderFails()
{

View File

@ -6,8 +6,6 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Microsoft.Extensions.DependencyInjection
@ -18,9 +16,7 @@ namespace Microsoft.Extensions.DependencyInjection
public void Configure_AssignsInvalidModelStateResponseFactory()
{
// Arrange
var optionsSetup = new ApiBehaviorOptionsSetup(
NullLoggerFactory.Instance,
Options.Options.Create(new MvcCompatibilityOptions()));
var optionsSetup = new ApiBehaviorOptionsSetup();
var options = new ApiBehaviorOptions();
// Act
@ -35,9 +31,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
// Arrange
var expected = new[] { 400, 401, 403, 404, 406, 409, 415, 422, };
var optionsSetup = new ApiBehaviorOptionsSetup(
NullLoggerFactory.Instance,
Options.Options.Create(new MvcCompatibilityOptions()));
var optionsSetup = new ApiBehaviorOptionsSetup();
var options = new ApiBehaviorOptions();
// Act
@ -51,9 +45,7 @@ namespace Microsoft.Extensions.DependencyInjection
public void PostConfigure_SetProblemDetailsModelStateResponseFactory()
{
// Arrange
var optionsSetup = new ApiBehaviorOptionsSetup(
NullLoggerFactory.Instance,
Options.Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest }));
var optionsSetup = new ApiBehaviorOptionsSetup();
var options = new ApiBehaviorOptions();
// Act
@ -68,9 +60,7 @@ namespace Microsoft.Extensions.DependencyInjection
public void PostConfigure_DoesNotSetProblemDetailsFactory_IfValueWasModified()
{
// Arrange
var optionsSetup = new ApiBehaviorOptionsSetup(
NullLoggerFactory.Instance,
Options.Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest }));
var optionsSetup = new ApiBehaviorOptionsSetup();
var options = new ApiBehaviorOptions();
Func<ActionContext, IActionResult> expected = _ => null;

View File

@ -33,11 +33,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
new[] { new DefaultFilterProvider() });
// Act
var cacheEntry1 = controllerActionInvokerCache.GetCachedResult(controllerContext);
var cacheEntry2 = controllerActionInvokerCache.GetCachedResult(controllerContext);
var (cacheEntry, filters) = controllerActionInvokerCache.GetCachedResult(controllerContext);
var (cacheEntry2, filters2) = controllerActionInvokerCache.GetCachedResult(controllerContext);
// Assert
Assert.Equal(cacheEntry1.filters, cacheEntry2.filters);
Assert.Equal(filters, filters2);
}
[Fact]
@ -54,11 +54,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
new[] { new DefaultFilterProvider() });
// Act
var cacheEntry1 = controllerActionInvokerCache.GetCachedResult(controllerContext);
var cacheEntry2 = controllerActionInvokerCache.GetCachedResult(controllerContext);
var (cacheEntry, filters) = controllerActionInvokerCache.GetCachedResult(controllerContext);
var (cacheEntry2, filters2) = controllerActionInvokerCache.GetCachedResult(controllerContext);
// Assert
Assert.Same(cacheEntry1.cacheEntry, cacheEntry2.cacheEntry);
Assert.Same(cacheEntry, cacheEntry2);
}
private class TestFilter : IFilterMetadata
@ -100,10 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
new[] { controllerContext.ActionDescriptor });
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var mvcOptions = Options.Create(new MvcOptions
{
AllowValidatingTopLevelNodes = true,
});
var mvcOptions = Options.Create(new MvcOptions());
return new ControllerActionInvokerCache(
descriptorProvider,

View File

@ -1,83 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Core.Infrastructure
{
public class MvcOptionsConfigureCompatibilityOptionsTest
{
[Fact]
public void PostConfigure_ConfiguresMaxValidationDepth()
{
// Arrange
var mvcOptions = new MvcOptions();
var mvcCompatibilityOptions = new MvcCompatibilityOptions
{
CompatibilityVersion = CompatibilityVersion.Version_3_0,
};
var configureOptions = new MvcOptionsConfigureCompatibilityOptions(
NullLoggerFactory.Instance,
Options.Create(mvcCompatibilityOptions));
// Act
configureOptions.PostConfigure(string.Empty, mvcOptions);
// Assert
Assert.Equal(32, mvcOptions.MaxValidationDepth);
}
[Fact]
public void PostConfigure_DoesNotConfiguresMaxValidationDepth_WhenSetToNull()
{
// Arrange
var mvcOptions = new MvcOptions
{
MaxValidationDepth = null,
};
var mvcCompatibilityOptions = new MvcCompatibilityOptions
{
CompatibilityVersion = CompatibilityVersion.Version_3_0,
};
var configureOptions = new MvcOptionsConfigureCompatibilityOptions(
NullLoggerFactory.Instance,
Options.Create(mvcCompatibilityOptions));
// Act
configureOptions.PostConfigure(string.Empty, mvcOptions);
// Assert
Assert.Null(mvcOptions.MaxValidationDepth);
}
[Fact]
public void PostConfigure_DoesNotConfiguresMaxValidationDepth_WhenSetToValue()
{
// Arrange
var expected = 13;
var mvcOptions = new MvcOptions
{
MaxValidationDepth = expected,
};
var mvcCompatibilityOptions = new MvcCompatibilityOptions
{
CompatibilityVersion = CompatibilityVersion.Version_3_0,
};
var configureOptions = new MvcOptionsConfigureCompatibilityOptions(
NullLoggerFactory.Instance,
Options.Create(mvcCompatibilityOptions));
// Act
configureOptions.PostConfigure(string.Empty, mvcOptions);
// Assert
Assert.Equal(expected, mvcOptions.MaxValidationDepth);
}
}
}

View File

@ -51,17 +51,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType(typeof(ArrayModelBinder<>).MakeGenericType(modelType.GetElementType()), result);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForArrayType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
[Fact]
public void Create_ForArrayType_ReturnsBinder()
{
// Arrange
var provider = new ArrayModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(int[]));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
Assert.Equal(typeof(int), m.ModelType);
@ -73,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert
var binder = Assert.IsType<ArrayModelBinder<int>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
Assert.True(binder.AllowValidatingTopLevelNodes);
}
[Fact]

View File

@ -48,11 +48,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{
// Arrange
var expectedErrorCount = isBindingRequired ? 1 : 0;
var binder = new ArrayModelBinder<string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
NullLoggerFactory.Instance,
@ -81,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert
Assert.Empty(Assert.IsType<string[]>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
Assert.Equal(expectedErrorCount, bindingContext.ModelState.ErrorCount);
}
[Fact]
@ -218,30 +220,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
}
private static IModelBinder CreateIntBinder()
{
return new StubModelBinder(context =>
{
var value = context.ValueProvider.GetValue(context.ModelName);
if (value != ValueProviderResult.None)
{
object valueToConvert = null;
if (value.Values.Count == 1)
{
valueToConvert = value.Values[0];
}
else if (value.Values.Count > 1)
{
valueToConvert = value.Values.ToArray();
}
var model = ModelBindingHelper.ConvertTo(valueToConvert, context.ModelType, value.Culture);
return ModelBindingResult.Success(model);
}
return ModelBindingResult.Failed();
});
}
private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider)
{
var bindingContext = CreateContext();
@ -277,4 +255,3 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
}
}

View File

@ -66,17 +66,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType<CollectionModelBinder<int>>(result);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
[Fact]
public void Create_ForSupportedType_ReturnsBinder()
{
// Arrange
var provider = new CollectionModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(List<int>));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
Assert.Equal(typeof(int), m.ModelType);
@ -88,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert
var binder = Assert.IsType<CollectionModelBinder<int>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
Assert.True(binder.AllowValidatingTopLevelNodes);
}
private class Person

View File

@ -55,17 +55,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.IsType<ComplexTypeModelBinder>(result);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
[Fact]
public void Create_ForSupportedType_ReturnsBinder()
{
// Arrange
var provider = new ComplexTypeModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(Person));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
if (m.ModelType == typeof(int) || m.ModelType == typeof(string))
@ -83,8 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<ComplexTypeModelBinder>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
Assert.IsType<ComplexTypeModelBinder>(result);
}
private class Person

View File

@ -57,40 +57,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var result = provider.GetBinder(context);
// Assert
Assert.IsType<DictionaryModelBinder<string, int>>(result);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Create_ForDictionaryType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes(
bool allowValidatingTopLevelNodes)
{
// Arrange
var provider = new DictionaryModelBinderProvider();
var context = new TestModelBinderProviderContext(typeof(Dictionary<string, string>));
context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes;
context.OnCreatingBinder(m =>
{
if (m.ModelType == typeof(KeyValuePair<string, string>) || m.ModelType == typeof(string))
{
return Mock.Of<IModelBinder>();
}
else
{
Assert.False(true, "Not the right model type");
return null;
}
});
// Act
var result = provider.GetBinder(context);
// Assert
var binder = Assert.IsType<DictionaryModelBinder<string, string>>(result);
Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes);
Assert.False(((CollectionModelBinder<KeyValuePair<string, string>>)binder).AllowValidatingTopLevelNodes);
var binder = Assert.IsType<DictionaryModelBinder<string, int>>(result);
Assert.False(binder.AllowValidatingTopLevelNodes); // work done in DictionaryModelBinder.
}
private class Person

View File

@ -349,11 +349,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject(
bool allowValidatingTopLevelNodes,
bool isBindingRequired)
{
// Arrange
var expectedErrorCount = isBindingRequired ? 1 : 0;
var binder = new DictionaryModelBinder<string, string>(
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance),
@ -383,7 +385,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// Assert
Assert.Empty(Assert.IsType<Dictionary<string, string>>(bindingContext.Result.Model));
Assert.True(bindingContext.Result.IsModelSet);
Assert.Equal(0, bindingContext.ModelState.ErrorCount);
Assert.Equal(expectedErrorCount, bindingContext.ModelState.ErrorCount);
}
[Fact]

View File

@ -14,55 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
public class HeaderModelBinderProviderTest
{
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(string[]))]
[InlineData(typeof(List<string>))]
public void Create_WhenBindingSourceIsFromHeader_ReturnsBinder_ForStringTypes_And_CompatVersion_2_0(
Type modelType)
{
// Arrange
var provider = new HeaderModelBinderProvider();
var testBinder = Mock.Of<IModelBinder>();
var context = GetTestModelBinderProviderContext(
modelType,
allowBindingHeaderValuesToNonStringModelTypes: false);
context.BindingInfo.BindingSource = BindingSource.Header;
// Act
var result = provider.GetBinder(context);
// Assert
Assert.IsType<HeaderModelBinder>(result);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(int?))]
[InlineData(typeof(IEnumerable<int>))]
[InlineData(typeof(double))]
[InlineData(typeof(double?))]
[InlineData(typeof(IEnumerable<double>))]
[InlineData(typeof(CarEnumType))]
[InlineData(typeof(CarEnumType?))]
[InlineData(typeof(IEnumerable<CarEnumType>))]
public void Create_WhenBindingSourceIsFromHeader_ReturnsNull_ForNonStringTypes_And_CompatVersion_2_0(
Type modelType)
{
// Arrange
var provider = new HeaderModelBinderProvider();
var context = GetTestModelBinderProviderContext(
modelType,
allowBindingHeaderValuesToNonStringModelTypes: false);
context.BindingInfo.BindingSource = BindingSource.Header;
// Act
var result = provider.GetBinder(context);
// Assert
Assert.Null(result);
}
public static TheoryData<BindingSource> NonHeaderBindingSources
{
get
@ -227,13 +178,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Null(result);
}
private TestModelBinderProviderContext GetTestModelBinderProviderContext(
Type modelType,
bool allowBindingHeaderValuesToNonStringModelTypes = true)
private TestModelBinderProviderContext GetTestModelBinderProviderContext(Type modelType)
{
var context = new TestModelBinderProviderContext(modelType);
var options = context.Services.GetRequiredService<IOptions<MvcOptions>>().Value;
options.AllowBindingHeaderValuesToNonStringModelTypes = allowBindingHeaderValuesToNonStringModelTypes;
return context;
}

View File

@ -22,11 +22,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{
get
{
var data = new TheoryData<HeaderModelBinder>();
var data = new TheoryData<HeaderModelBinder>
{
#pragma warning disable CS0618
data.Add(new HeaderModelBinder());
new HeaderModelBinder(),
#pragma warning restore CS0618
data.Add(new HeaderModelBinder(NullLoggerFactory.Instance));
new HeaderModelBinder(NullLoggerFactory.Instance),
};
return data;
}
}
@ -175,11 +178,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
[Theory]
[InlineData(typeof(CarType?), null)]
[InlineData(typeof(int?), null)]
public async Task HeaderBinder_DoesNotSetModel_ForHeaderNotPresentOnRequest(
Type modelType,
object expectedModel)
[InlineData(typeof(CarType?))]
[InlineData(typeof(int?))]
public async Task HeaderBinder_DoesNotSetModel_ForHeaderNotPresentOnRequest(Type modelType)
{
// Arrange
var bindingContext = CreateContext(modelType);
@ -194,11 +195,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
}
[Theory]
[InlineData(typeof(string[]), null)]
[InlineData(typeof(IEnumerable<string>), null)]
public async Task HeaderBinder_DoesNotCreateEmptyCollection_ForNonTopLevelObjects(
Type modelType,
object expectedModel)
[InlineData(typeof(string[]))]
[InlineData(typeof(IEnumerable<string>))]
public async Task HeaderBinder_DoesNotCreateEmptyCollection_ForNonTopLevelObjects(Type modelType)
{
// Arrange
var bindingContext = CreateContext(modelType);
@ -344,30 +343,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Assert.Equal($"The value '{headerValues[1]}' is not valid.", entry.Errors[1].ErrorMessage);
}
private static DefaultModelBindingContext CreateContext(
Type modelType,
bool allowBindingHeaderValuesToNonStringModelTypes = true)
private static DefaultModelBindingContext CreateContext(Type modelType)
{
return CreateContext(
metadata: GetMetadataForType(modelType),
valueProvider: null,
allowBindingHeaderValuesToNonStringModelTypes: allowBindingHeaderValuesToNonStringModelTypes);
return CreateContext(metadata: GetMetadataForType(modelType), valueProvider: null);
}
private static DefaultModelBindingContext CreateContext(
ModelMetadata metadata,
IValueProvider valueProvider = null,
bool allowBindingHeaderValuesToNonStringModelTypes = true)
IValueProvider valueProvider = null)
{
if (valueProvider == null)
{
valueProvider = Mock.Of<IValueProvider>();
}
var options = new MvcOptions()
{
AllowBindingHeaderValuesToNonStringModelTypes = allowBindingHeaderValuesToNonStringModelTypes
};
var options = new MvcOptions();
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
setup.Configure(options);
@ -452,4 +442,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Coupe
}
}
}
}

View File

@ -27,10 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
public class ParameterBinderTest
{
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(new MvcOptions
{
AllowValidatingTopLevelNodes = true,
});
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(new MvcOptions());
public static TheoryData BindModelAsyncData
{
@ -211,37 +208,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
}
[Fact]
public async Task BindModelAsync_DoesNotEnforceTopLevelBindRequired_IfNotValidatingTopLevelNodes()
{
// Arrange
var actionContext = GetControllerContext();
var mockModelMetadata = CreateMockModelMetadata();
mockModelMetadata.Setup(o => o.IsBindingRequired).Returns(true);
// Bind attribute errors are phrased in terms of the model name, not display name
mockModelMetadata.Setup(o => o.DisplayName).Returns("Ignored Display Name");
// Do not set AllowValidatingTopLevelNodes.
var optionsAccessor = Options.Create(new MvcOptions());
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, optionsAccessor: optionsAccessor);
var modelBindingResult = ModelBindingResult.Failed();
// Act
var result = await parameterBinder.BindModelAsync(
actionContext,
CreateMockModelBinder(modelBindingResult),
CreateMockValueProvider(),
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
mockModelMetadata.Object,
"ignoredvalue");
// Assert
Assert.True(actionContext.ModelState.IsValid);
Assert.Empty(actionContext.ModelState);
}
[Fact]
public async Task BindModelAsync_EnforcesTopLevelRequired()
{
@ -280,43 +246,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
}
[Fact]
public async Task BindModelAsync_DoesNotEnforceTopLevelRequired_IfNotValidatingTopLevelNodes()
{
// Arrange
var actionContext = GetControllerContext();
var mockModelMetadata = CreateMockModelMetadata();
mockModelMetadata.Setup(o => o.IsRequired).Returns(true);
mockModelMetadata.Setup(o => o.DisplayName).Returns("My Display Name");
mockModelMetadata.Setup(o => o.ValidatorMetadata).Returns(new[]
{
new RequiredAttribute()
});
var validator = new DataAnnotationsModelValidator(
new ValidationAttributeAdapterProvider(),
new RequiredAttribute(),
stringLocalizer: null);
// Do not set AllowValidatingTopLevelNodes.
var optionsAccessor = Options.Create(new MvcOptions());
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator, optionsAccessor);
var modelBindingResult = ModelBindingResult.Success(null);
// Act
var result = await parameterBinder.BindModelAsync(
actionContext,
CreateMockModelBinder(modelBindingResult),
CreateMockValueProvider(),
new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
mockModelMetadata.Object,
"ignoredvalue");
// Assert
Assert.True(actionContext.ModelState.IsValid);
Assert.Empty(actionContext.ModelState);
}
public static TheoryData<RequiredAttribute, ParameterDescriptor, ModelMetadata> EnforcesTopLevelRequiredDataSet
{
get

View File

@ -598,17 +598,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
[Fact]
public async Task ReadAsync_DefaultOptions_DoesNotWrapJsonInputExceptions()
public async Task ReadAsync_DoNotAllowInputFormatterExceptionMessages_DoesNotWrapJsonInputExceptions()
{
// Arrange
var formatter = new JsonInputFormatter(
GetLogger(),
_serializerSettings,
ArrayPool<char>.Shared,
_objectPoolProvider,
new MvcOptions(),
new MvcJsonOptions());
var formatter = CreateFormatter(allowInputFormatterExceptionMessages: false);
var contentBytes = Encoding.UTF8.GetBytes("{");
var httpContext = GetHttpContext(contentBytes);

View File

@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Administration/EitherCookie");
request.Headers.Add("Cookie", cookie2);
// Act 2: Will succeed because, with AllowCombiningAuthorizeFilters true, [Authorize] allows either cookie.
// Act 2: Will succeed because [Authorize] allows either cookie.
response = await Client.SendAsync(request);
// Assert 2

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var actionContext = new ActionContext(GetHttpContext(), new RouteData(), new ControllerActionDescriptor());
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, action.Filters);
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, new[] { authorizeFilter });
// Act
await authorizeFilter.OnAuthorizationAsync(authorizationFilterContext);
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var authorizeData = action.Attributes.OfType<AuthorizeAttribute>();
var authorizeFilter = new AuthorizeFilter(policyProvider, authorizeData);
var actionContext = new ActionContext(GetHttpContext(combineAuthorize: true), new RouteData(), new ControllerActionDescriptor());
var actionContext = new ActionContext(GetHttpContext(), new RouteData(), new ControllerActionDescriptor());
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, action.Filters);
@ -89,11 +89,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Equal(4, policyProvider.GetPolicyCount);
}
private HttpContext GetHttpContext(bool combineAuthorize = false)
private HttpContext GetHttpContext()
{
var httpContext = new DefaultHttpContext();
var httpContext = new DefaultHttpContext
{
RequestServices = GetServices()
};
httpContext.RequestServices = GetServices(combineAuthorize);
return httpContext;
}
@ -108,11 +110,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return context;
}
private static IServiceProvider GetServices(bool combineAuthorize)
private static IServiceProvider GetServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddAuthorization();
serviceCollection.AddMvc(o => o.AllowCombiningAuthorizeFilters = combineAuthorize);
serviceCollection.AddMvc();
serviceCollection
.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance)
.AddTransient<ILogger<DefaultAuthorizationService>, Logger<DefaultAuthorizationService>>()

View File

@ -189,51 +189,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
}
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with an action parameter's
// type. This should behave identically to such an attribute on an action parameter. (Tests such as
// BindParameter_WithData_WithPrefix_GetsBound cover associating [ModelBinder] with an action parameter.)
//
// This is a regression test for aspnet/Mvc#4652
[Theory]
[MemberData(nameof(NullAndEmptyBindingInfo))]
public async Task BinderTypeOnParameterType_WithDataEmptyPrefixAndVersion20_GetsBound(
BindingInfo bindingInfo)
{
// Arrange
var testContext = ModelBindingTestHelper.GetTestContext(
// ParameterBinder will use ModelMetadata for typeof(Address), not Parameter1's ParameterInfo.
updateOptions: options => options.AllowValidatingTopLevelNodes = false);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = bindingInfo,
ParameterType = typeof(Address),
};
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
// ModelBindingResult
Assert.True(modelBindingResult.IsModelSet);
// Model
var address = Assert.IsType<Address>(modelBindingResult.Model);
Assert.Equal("SomeStreet", address.Street);
// ModelState
Assert.True(modelState.IsValid);
var kvp = Assert.Single(modelState);
Assert.Equal("Street", kvp.Key);
var entry = kvp.Value;
Assert.NotNull(entry);
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
}
private class Person3
{
[ModelBinder(BinderType = typeof(Address3ModelBinder))]

View File

@ -41,8 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
ModelMetadata metadata;
if (optionsAccessor.Value.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameterInfo != null)
{
metadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo);

View File

@ -50,4 +50,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public MvcOptions Value { get; }
}
}
}

View File

@ -1705,7 +1705,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public async Task FromBody_JToken_ExcludedFromValidation()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(new TestMvcOptions().Value);
var options = new TestMvcOptions().Value;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(options);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
@ -1718,11 +1719,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
};
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
updateRequest: request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ message: \"Hello\" }"));
request.ContentType = "application/json";
});
},
mvcOptions: options);
var httpContext = testContext.HttpContext;
var modelState = testContext.ModelState;

View File

@ -169,11 +169,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
var defaultProvider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
defaultProvider.OnProvidersExecuting(context);
return context;
}
}
}
}

View File

@ -881,42 +881,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
});
}
[Fact]
public void CreateHandlerMethods_WithLegacyValidationBehavior_AddsParameterDescriptors()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }),
Options.Create(new RazorPagesOptions()));
var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo();
var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost));
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]);
// Act
provider.PopulateHandlerMethods(pageModel);
// Assert
var handlerMethods = pageModel.HandlerMethods;
var handler = Assert.Single(handlerMethods);
Assert.Collection(
handler.Parameters,
p =>
{
Assert.NotNull(p.ParameterInfo);
Assert.Equal(typeof(string), p.ParameterInfo.ParameterType);
Assert.Equal("name", p.ParameterName);
},
p =>
{
Assert.NotNull(p.ParameterInfo);
Assert.Equal(typeof(int), p.ParameterInfo.ParameterType);
Assert.Equal("id", p.ParameterName);
Assert.Equal("personId", p.BindingInfo.BinderModelName);
});
}
private class PageWithHandlerParameters
{
public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { }
@ -1078,8 +1042,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
// Arrange
var provider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions()),
Options.Create(new RazorPagesOptions()));
var typeInfo = typeof(object).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
@ -1225,7 +1189,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
return new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
}
}

View File

@ -6,7 +6,6 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -145,11 +144,12 @@ namespace Microsoft.AspNetCore.Mvc.Filters
{
var defaultProvider = new DefaultPageApplicationModelProvider(
TestModelMetadataProvider.CreateDefaultProvider(),
Options.Create(new MvcOptions()),
Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true }));
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo);
defaultProvider.OnProvidersExecuting(context);
return context;
}
}
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
@ -461,10 +460,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var mvcOptions = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
var mvcOptions = new MvcOptions();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,

View File

@ -1544,15 +1544,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var mvcOptions = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
var mvcOptions = new MvcOptions();
return new ParameterBinder(
metadataProvider,
factory,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
new DefaultObjectValidator(metadataProvider, new[] { validator }, mvcOptions),
Options.Create(mvcOptions),
NullLoggerFactory.Instance);
}

View File

@ -21,10 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class PageBinderFactoryTest
{
private static readonly MvcOptions _options = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
private static readonly MvcOptions _options = new MvcOptions();
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(_options);
[Fact]
@ -712,56 +709,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
});
}
[Fact]
public async Task CreateHandlerBinder_DoesNotValidateTopLevelParameters_IfDisabled()
{
// Arrange
var type = typeof(PageModelWithExecutors);
var actionDescriptor = GetActionDescriptorWithHandlerMethod(
type,
nameof(PageModelWithExecutors.OnPostWithValidation));
// Act
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var mvcOptions = new MvcOptions();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
Options.Create(mvcOptions),
NullLoggerFactory.Instance);
var factory = PageBinderFactory.CreateHandlerBinder(
parameterBinder,
modelMetadataProvider,
modelBinderFactory,
actionDescriptor,
actionDescriptor.HandlerMethods[0],
mvcOptions);
var page = new PageWithProperty
{
PageContext = GetPageContext()
};
var model = new PageModelWithExecutors();
var arguments = new Dictionary<string, object>();
// Act
await factory(page.PageContext, arguments);
// Assert
var modelState = page.PageContext.ModelState;
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
}
private static CompiledPageActionDescriptor GetActionDescriptorWithHandlerMethod(Type type, string method)
{
var handlerMethodInfo = type.GetMethod(method);
@ -923,24 +870,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public string PropertyWithNoValue { get; set; }
}
private class PageModelWithModelBinderAttribute
{
[ModelBinder(BinderType = typeof(DeclarativeSecurityAction))]
public Guid PropertyWithBinderType { get; set; }
}
private class PageModelWithPropertyFilterAttribute
{
[ModelBinder]
[TestPropertyFilterProvider]
public object PropertyWithFilter { get; set; }
}
private class TestPropertyFilterProvider : Attribute, IPropertyFilterProvider
{
public Func<ModelMetadata, bool> PropertyFilter => _ => true;
}
private class PageModelWithDefaultValue
{
[ModelBinder]

View File

@ -43,20 +43,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
Assert.True(mvcOptions.AllowBindingHeaderValuesToNonStringModelTypes);
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.True(razorPagesOptions.AllowAreas);
Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
Assert.False(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery);
}
[Fact]
@ -81,20 +73,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
var xmlOptions = services.GetRequiredService<IOptions<MvcXmlOptions>>().Value;
// Assert
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
Assert.True(mvcOptions.AllowBindingHeaderValuesToNonStringModelTypes);
Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType);
Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy);
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.True(razorPagesOptions.AllowAreas);
Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests);
Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat);
Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent);
Assert.False(apiBehaviorOptions.AllowInferringBindingSourceForCollectionTypesAsFromQuery);
}
// This just does the minimum needed to be able to resolve these options.

View File

@ -389,13 +389,6 @@ namespace Microsoft.AspNetCore.Mvc
typeof(RazorPagesOptionsConfigureCompatibilityOptions),
}
},
{
typeof(IPostConfigureOptions<MvcJsonOptions>),
new[]
{
typeof(MvcJsonOptionsConfigureCompatibilityOptions),
}
},
{
typeof(IActionConstraintProvider),
new Type[]

View File

@ -16,7 +16,7 @@ namespace BasicWebSite
services.AddRouting();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Latest) // this compat version enables endpoint routing
.SetCompatibilityVersion(CompatibilityVersion.Latest)
.AddXmlDataContractSerializerFormatters();
services.ConfigureBaseWebSiteAuthPolicies();
@ -45,4 +45,4 @@ namespace BasicWebSite
});
}
}
}
}

View File

@ -20,8 +20,7 @@ namespace SecurityWebSite.Controllers
return Content("Administration.Index");
}
// Either cookie should allow access to this action (if AllowCombiningAuthorizeFilters is true)
// If AllowCombiningAuthorizeFilters is false, the main cookie is required.
// Either cookie should allow access to this action.
[Authorize(AuthenticationSchemes = "Cookie2")]
public IActionResult EitherCookie()
{