Add compatibility switch controlling parameter metadata and top-level validation

- #7413 part 1 of 2
- made all `ModelMetadataProvider` and `ObjectModelValidator`-specific code conditional
  - fortunately, `MvcOptions` easy to get; affected code is primarily `internal` or pub-`Internal`
  - remove unnecessary `ModelMetadataProvider` use in `ApiBehaviorApplicationModelProvider`
- run integration and functional tests with `CompatibilityVersion.Version_2_1`
  - functional test change depends on @javiercn's recent #7541 fix
  - remove test code now redundantly turning compatibility switches on

nits:
- correct spelling errors in `CompatibilitySwitch`
- take VS suggestions, mostly in test code
- rename methods in `ControllerBinderDelegateProviderTest` to match current API
- slightly refactor in `ApiBehaviorApplicationModelProvider`
This commit is contained in:
Doug Bunting 2018-03-23 16:09:53 -07:00
parent 51784bb2d6
commit 5e245da326
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
31 changed files with 672 additions and 351 deletions

View File

@ -24,10 +24,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
/// </summary>
public class DefaultApiDescriptionProvider : IApiDescriptionProvider
{
private readonly IList<IInputFormatter> _inputFormatters;
private readonly IList<IOutputFormatter> _outputFormatters;
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly MvcOptions _mvcOptions;
private readonly IInlineConstraintResolver _constraintResolver;
private readonly IModelMetadataProvider _modelMetadataProvider;
/// <summary>
/// Creates a new instance of <see cref="DefaultApiDescriptionProvider"/>.
@ -41,8 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
IInlineConstraintResolver constraintResolver,
IModelMetadataProvider modelMetadataProvider)
{
_inputFormatters = optionsAccessor.Value.InputFormatters;
_outputFormatters = optionsAccessor.Value.OutputFormatters;
_mvcOptions = optionsAccessor.Value;
_constraintResolver = constraintResolver;
_modelMetadataProvider = modelMetadataProvider;
}
@ -164,7 +162,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
ModelMetadata metadata = null;
if (actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
if (_mvcOptions.AllowValidatingTopLevelNodes &&
actionParameter is ControllerParameterDescriptor controllerParameterDescriptor &&
_modelMetadataProvider is ModelMetadataProvider provider)
{
// The default model metadata provider derives from ModelMetadataProvider
@ -344,7 +343,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
var results = new List<ApiRequestFormat>();
foreach (var contentType in contentTypes)
{
foreach (var formatter in _inputFormatters)
foreach (var formatter in _mvcOptions.InputFormatters)
{
if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider)
{
@ -422,7 +421,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
contentTypes.Add((string)null);
}
var responseTypeMetadataProviders = _outputFormatters.OfType<IApiResponseTypeMetadataProvider>();
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
foreach (var objectType in objectTypes)
{
@ -779,4 +778,4 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
}
}
}
}
}

View File

@ -17,16 +17,16 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
// behaviors in a minor release.
//
// To make things better for future users, we also want to provide an easy way for applications to get
// access to the new behaviors. We make changes when they are improvments, and if we're changing something
// access to the new behaviors. We make changes when they are improvements, and if we're changing something
// we've already shipped, it must add value for all of our users (eventually). To this end, new applications
// created using the template are always opted in to the 'current' version.
//
// This means that all changes in behavior should be opt-in.
// This means that all changes in behavior should be opt-in.
//
// -----
//
// Moving on from general philosophy, here's how to implement a behavior change and corresponding
// compatibility switch.
//
// Moving on from general philosophy, here's how to implement a behavior change and corresponding
// compatibility switch.
//
// Add a new property on options that uses a CompatibilitySwitch<T> as a backing field. Make sure the
// new switch is exposed by implementing IEnumerable<ICompatibilitySwitch> on the options class. Pass the
@ -34,33 +34,33 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
//
// Choose a boolean value or a new enum type as the 'value' of the property.
//
// If the new property has a boolean value, it should be named something like `SuppressFoo`
// If the new property has a boolean value, it should be named something like `SuppressFoo`
// (if the new value deactivates some behavior) or like `AllowFoo` (if the new value enables some behavior).
// Choose a name so that the old behavior equates to 'false'.
//
// If it's an enum, make sure you initialize the compatibility switch using the
// CompatibilitySwitch(string, value) constructor to make it obvious the correct value is passed in. It's
// If it's an enum, make sure you initialize the compatibility switch using the
// CompatibilitySwitch(string, value) constructor to make it obvious the correct value is passed in. It's
// a good idea to equate the original behavior with the default enum value as well.
//
// Then create (or modify) a subclass of ConfigureCompatibilityOptions appropriate for your options type.
// Then create (or modify) a subclass of ConfigureCompatibilityOptions appropriate for your options type.
// Override the DefaultValues property and provide appropriate values based on the value of the Version
// property. If you just added this class, register it as an IPostConfigureOptions<TOptions> in DI.
//
/// <summary>
/// Infrastructure supporting the implementation of <see cref="CompatibilityVersion"/>. This is an
/// implementation of <see cref="ICompatibilitySwitch"/> suitible for use with the <see cref="IOptions{T}"/>
/// implementation of <see cref="ICompatibilitySwitch"/> suitable for use with the <see cref="IOptions{T}"/>
/// pattern. This is framework infrastructure and should not be used by application code.
/// </summary>
/// <typeparam name="TValue">The type of value assoicated with the compatibility switch.</typeparam>
/// <typeparam name="TValue">The type of value associated with the compatibility switch.</typeparam>
public class CompatibilitySwitch<TValue> : ICompatibilitySwitch where TValue : struct
{
private TValue _value;
/// <summary>
/// Creates a new compatiblity switch with the provided name.
/// Creates a new compatibility switch with the provided name.
/// </summary>
/// <param name="name">
/// The compatiblity switch name. The name must match a property name on an options type.
/// The compatibility switch name. The name must match a property name on an options type.
/// </param>
public CompatibilitySwitch(string name)
: this(name, default)
@ -68,10 +68,10 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
}
/// <summary>
/// Creates a new compatiblity switch with the provided name and initial value.
/// Creates a new compatibility switch with the provided name and initial value.
/// </summary>
/// <param name="name">
/// The compatiblity switch name. The name must match a property name on an options type.
/// The compatibility switch name. The name must match a property name on an options type.
/// </param>
/// <param name="initialValue">
/// The initial value to assign to the switch.

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public MvcOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
values[nameof(MvcOptions.AllowCombiningAuthorizeFilters)] = true;
values[nameof(MvcOptions.AllowBindingHeaderValuesToNonStringModelTypes)] = true;
values[nameof(MvcOptions.AllowValidatingTopLevelNodes)] = true;
values[nameof(MvcOptions.InputFormatterExceptionPolicy)] = InputFormatterExceptionPolicy.MalformedInputExceptions;
values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true;
}

View File

@ -210,22 +210,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
}
// internal for testing
internal void InferParameterModelPrefixes(ActionModel actionModel)
{
foreach (var parameter in actionModel.Parameters)
{
if (parameter.BindingInfo != null &&
parameter.BindingInfo.BinderModelName == null &&
parameter.BindingInfo.BindingSource != null &&
!parameter.BindingInfo.BindingSource.IsGreedy)
var bindingInfo = parameter.BindingInfo;
if (bindingInfo?.BindingSource != null &&
bindingInfo.BinderModelName == null &&
!bindingInfo.BindingSource.IsGreedy &&
IsComplexTypeParameter(parameter))
{
var metadata = GetParameterMetadata(parameter);
if (metadata.IsComplexType)
{
parameter.BindingInfo.BinderModelName = string.Empty;
}
parameter.BindingInfo.BinderModelName = string.Empty;
}
}
}
@ -233,25 +230,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Internal for unit testing.
internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
{
var parameterType = parameter.ParameterInfo.ParameterType;
if (ParameterExistsInAllRoutes(parameter.Action, parameter.ParameterName))
{
return BindingSource.Path;
}
else
{
var parameterMetadata = GetParameterMetadata(parameter);
if (parameterMetadata != null)
{
var bindingSource = parameterMetadata.IsComplexType ?
BindingSource.Body :
BindingSource.Query;
return bindingSource;
}
}
var bindingSource = IsComplexTypeParameter(parameter) ?
BindingSource.Body :
BindingSource.Query;
return null;
return bindingSource;
}
private bool ParameterExistsInAllRoutes(ActionModel actionModel, string parameterName)
@ -277,16 +265,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return parameterExistsInSomeRoute;
}
private ModelMetadata GetParameterMetadata(ParameterModel parameter)
private bool IsComplexTypeParameter(ParameterModel parameter)
{
if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProvider)
{
return modelMetadataProvider.GetMetadataForParameter(parameter.ParameterInfo);
}
else
{
return _modelMetadataProvider.GetMetadataForType(parameter.ParameterInfo.ParameterType);
}
// No need for information from attributes on the parameter. Just use its type.
return _modelMetadataProvider
.GetMetadataForType(parameter.ParameterInfo.ParameterType)
.IsComplexType;
}
}
}

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Internal
{
@ -21,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IFilterProvider[] _filterProviders;
private readonly IControllerFactoryProvider _controllerFactoryProvider;
private readonly MvcOptions _mvcOptions;
private volatile InnerCache _currentCache;
public ControllerActionInvokerCache(
@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
IEnumerable<IFilterProvider> filterProviders,
IControllerFactoryProvider factoryProvider)
IControllerFactoryProvider factoryProvider,
IOptions<MvcOptions> mvcOptions)
{
_collectionProvider = collectionProvider;
_parameterBinder = parameterBinder;
@ -38,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_modelMetadataProvider = modelMetadataProvider;
_filterProviders = filterProviders.OrderBy(item => item.Order).ToArray();
_controllerFactoryProvider = factoryProvider;
_mvcOptions = mvcOptions.Value;
}
private InnerCache CurrentCache
@ -82,13 +85,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_parameterBinder,
_modelBinderFactory,
_modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_mvcOptions);
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
cacheEntry = new ControllerActionInvokerCacheEntry(
filterFactoryResult.CacheableFilters,
controllerFactory,
filterFactoryResult.CacheableFilters,
controllerFactory,
controllerReleaser,
propertyBinderFactory,
objectMethodExecutor,

View File

@ -16,13 +16,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ParameterBinder parameterBinder,
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
ControllerActionDescriptor actionDescriptor)
ControllerActionDescriptor actionDescriptor,
MvcOptions mvcOptions)
{
if (parameterBinder == null)
{
throw new ArgumentNullException(nameof(parameterBinder));
}
if (modelBinderFactory == null)
{
throw new ArgumentNullException(nameof(modelBinderFactory));
}
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
@ -33,7 +39,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(actionDescriptor));
}
var parameterBindingInfo = GetParameterBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor);
if (mvcOptions == null)
{
throw new ArgumentNullException(nameof(mvcOptions));
}
var parameterBindingInfo = GetParameterBindingInfo(
modelBinderFactory,
modelMetadataProvider,
actionDescriptor,
mvcOptions);
var propertyBindingInfo = GetPropertyBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor);
if (parameterBindingInfo == null && propertyBindingInfo == null)
@ -104,7 +119,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static BinderItem[] GetParameterBindingInfo(
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
ControllerActionDescriptor actionDescriptor)
ControllerActionDescriptor actionDescriptor,
MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == 0)
@ -118,8 +134,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameter = parameters[i];
ModelMetadata metadata;
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase
&& parameter is ControllerParameterDescriptor controllerParameterDescriptor)
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <summary>
/// Initializes a new instance of <see cref="ObjectModelValidator"/>.
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="ModelMetadataProvider"/>.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="validatorProviders">The list of <see cref="IModelValidatorProvider"/>.</param>
public ObjectModelValidator(
IModelMetadataProvider modelMetadataProvider,

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
@ -18,23 +19,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly MvcOptions _mvcOptions;
private readonly IObjectModelValidator _objectModelValidator;
/// <summary>
/// <para>This constructor is obsolete and will be removed in a future version. The recommended alternative
/// is the overload that also takes an <see cref="ILoggerFactory"/>.</para>
/// is the overload that also takes a <see cref="MvcOptions"/> accessor and an <see cref="ILoggerFactory"/>.
/// </para>
/// <para>Initializes a new instance of <see cref="ParameterBinder"/>.</para>
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative"
+ " is the overload that also takes an " + nameof(ILoggerFactory) + ".")]
+ " is the overload that also takes a " + nameof(MvcOptions) + " accessor and an "
+ nameof(ILoggerFactory) + " .")]
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IObjectModelValidator validator)
: this(modelMetadataProvider, modelBinderFactory, validator, NullLoggerFactory.Instance)
: this(
modelMetadataProvider,
modelBinderFactory,
validator,
Options.Create(new MvcOptions()),
NullLoggerFactory.Instance)
{
}
@ -44,11 +53,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
/// <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>
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IObjectModelValidator validator,
IOptions<MvcOptions> mvcOptions,
ILoggerFactory loggerFactory)
{
if (modelMetadataProvider == null)
@ -66,6 +77,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(validator));
}
if (mvcOptions == null)
{
throw new ArgumentNullException(nameof(mvcOptions));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
@ -74,6 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
_modelMetadataProvider = modelMetadataProvider;
_modelBinderFactory = modelBinderFactory;
_objectModelValidator = validator;
_mvcOptions = mvcOptions.Value;
Logger = loggerFactory.CreateLogger(GetType());
}
@ -224,8 +241,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var modelBindingResult = modelBindingContext.Result;
var baseObjectValidator = _objectModelValidator as ObjectModelValidator;
if (baseObjectValidator == null)
if (_mvcOptions.AllowValidatingTopLevelNodes &&
_objectModelValidator is ObjectModelValidator baseObjectValidator)
{
Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
EnforceBindRequiredAndValidate(
baseObjectValidator,
actionContext,
metadata,
modelBindingContext,
modelBindingResult);
Logger.DoneAttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
}
else
{
// For legacy implementations (which directly implemented IObjectModelValidator), fall back to the
// back-compatibility logic. In this scenario, top-level validation attributes will be ignored like
@ -239,19 +269,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
modelBindingResult.Model);
}
}
else
{
Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
EnforceBindRequiredAndValidate(
baseObjectValidator,
actionContext,
metadata,
modelBindingContext,
modelBindingResult);
Logger.DoneAttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
}
return modelBindingResult;
}

View File

@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc
// 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 ICompatibilitySwitch[] _switches;
@ -50,6 +51,7 @@ namespace Microsoft.AspNetCore.Mvc
_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));
@ -57,6 +59,7 @@ namespace Microsoft.AspNetCore.Mvc
{
_allowCombiningAuthorizeFilters,
_allowBindingHeaderValuesToNonStringModelTypes,
_allowValidatingTopLevelNodes,
_inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType,
};
@ -89,8 +92,8 @@ namespace Microsoft.AspNetCore.Mvc
/// are applied.
/// </para>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
@ -114,15 +117,15 @@ namespace Microsoft.AspNetCore.Mvc
/// <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="Int32"/>,
/// <see cref="Enum"/>, <see cref="Boolean"/> etc.) or a collection of simple types. The default value of the
/// <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. The default value of the
/// property is <c>false</c>.
/// </summary>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
@ -144,6 +147,41 @@ namespace Microsoft.AspNetCore.Mvc
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"/> if the version is
/// <see cref="CompatibilityVersion.Version_2_1"/> or later; <see langword="false"/> otherwise.
/// </value>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have the value <see langword="false"/> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// </remarks>
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.
@ -173,8 +211,8 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
@ -204,13 +242,13 @@ namespace Microsoft.AspNetCore.Mvc
public FormatterCollection<IInputFormatter> InputFormatters { get; }
/// <summary>
/// Gets or sets an value indicating whether the model binding system will bind undefined values to
/// Gets or sets a value indicating whether the model binding system will bind undefined values to
/// enum types. The default value of the property is <c>false</c>.
/// </summary>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>

View File

@ -6,7 +6,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
@ -37,6 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private readonly ParameterBinder _parameterBinder;
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly ITempDataDictionaryFactory _tempDataFactory;
private readonly MvcOptions _mvcOptions;
private readonly HtmlHelperOptions _htmlHelperOptions;
private readonly IPageHandlerMethodSelector _selector;
private readonly RazorProjectFileSystem _razorFileSystem;
@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_parameterBinder = parameterBinder;
_modelMetadataProvider = modelMetadataProvider;
_tempDataFactory = tempDataFactory;
_mvcOptions = mvcOptions.Value;
_htmlHelperOptions = htmlHelperOptions.Value;
_selector = selector;
_razorFileSystem = razorFileSystem;
@ -261,7 +262,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_modelMetadataProvider,
_modelBinderFactory,
actionDescriptor,
actionDescriptor.HandlerMethods[i]);
actionDescriptor.HandlerMethods[i],
_mvcOptions);
}
return results;

View File

@ -90,7 +90,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
CompiledPageActionDescriptor actionDescriptor,
HandlerMethodDescriptor handler)
HandlerMethodDescriptor handler,
MvcOptions mvcOptions)
{
if (handler.Parameters == null || handler.Parameters.Count == 0)
{
@ -103,7 +104,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
var parameter = handler.Parameters[i];
ModelMetadata metadata;
if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.

View File

@ -326,8 +326,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
{
// Arrange
var action = CreateActionDescriptor();
action.AttributeRouteInfo = new AttributeRouteInfo();
action.AttributeRouteInfo.Template = template;
action.AttributeRouteInfo = new AttributeRouteInfo
{
Template = template
};
// Act
var descriptions = GetApiDescriptions(action);
@ -342,8 +344,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
{
// Arrange
var action = CreateActionDescriptor();
action.AttributeRouteInfo = new AttributeRouteInfo();
action.AttributeRouteInfo.Template = "api/Products/{id1}-{id2:int}";
action.AttributeRouteInfo = new AttributeRouteInfo
{
Template = "api/Products/{id1}-{id2:int}"
};
// Act
var descriptions = GetApiDescriptions(action);
@ -364,8 +368,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
{
// Arrange
var action = CreateActionDescriptor();
action.AttributeRouteInfo = new AttributeRouteInfo();
action.AttributeRouteInfo.Template = "api/Products/{id1}-{id2}/{id3:int}/{id4:int?}/{*id5:int}";
action.AttributeRouteInfo = new AttributeRouteInfo
{
Template = "api/Products/{id1}-{id2}/{id3:int}/{id4:int?}/{*id5:int}"
};
// Act
var descriptions = GetApiDescriptions(action);
@ -685,8 +691,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
// Arrange
var action = CreateActionDescriptor(methodName);
var filter = new ProducesResponseTypeAttribute(typeof(void), statusCode: 204);
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(filter, FilterScope.Action)
};
// Act
var descriptions = GetApiDescriptions(action);
@ -717,8 +725,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
Type = typeof(Order)
};
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(filter, FilterScope.Action)
};
// Act
var descriptions = GetApiDescriptions(action);
@ -758,8 +768,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
// Arrange
var action = CreateActionDescriptor(nameof(ReturnsProduct));
var expectedMediaTypes = new[] { "text/json", "text/xml" };
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action)
};
// Act
var descriptions = GetApiDescriptions(action);
@ -780,8 +792,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
Type = typeof(Order)
};
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(filter, FilterScope.Action)
};
var formatters = CreateOutputFormatters();
@ -843,8 +857,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
// Arrange
var action = CreateActionDescriptor(nameof(AcceptsProduct_Body));
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action)
};
// Act
var descriptions = GetApiDescriptions(action);
@ -863,8 +879,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
// Arrange
var action = CreateActionDescriptor(nameof(AcceptsProduct_Body));
action.FilterDescriptors = new List<FilterDescriptor>();
action.FilterDescriptors.Add(new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action));
action.FilterDescriptors = new List<FilterDescriptor>
{
new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action)
};
var formatters = CreateInputFormatters();
@ -914,7 +932,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
}
[Fact]
public void GetApiDescription_ParameterDescription_IsRequired()
public void GetApiDescription_ParameterDescription_IsRequiredSet()
{
// Arrange
var action = CreateActionDescriptor(nameof(RequiredParameter));
@ -932,6 +950,25 @@ namespace Microsoft.AspNetCore.Mvc.Description
Assert.True(parameter.ModelMetadata.IsBindingRequired);
}
[Fact]
public void GetApiDescription_ParameterDescription_IsRequiredNotSet_IfNotValiatingTopLevelNodes()
{
// 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()
{
@ -1382,11 +1419,15 @@ namespace Microsoft.AspNetCore.Mvc.Description
private IReadOnlyList<ApiDescription> GetApiDescriptions(
ActionDescriptor action,
List<MockInputFormatter> inputFormatters = null,
List<MockOutputFormatter> outputFormatters = null)
List<MockOutputFormatter> outputFormatters = null,
bool allowValidatingTopLevelNodes = true)
{
var context = new ApiDescriptionProviderContext(new ActionDescriptor[] { action });
var options = new MvcOptions();
var options = new MvcOptions
{
AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes,
};
foreach (var formatter in inputFormatters ?? CreateInputFormatters())
{
options.InputFormatters.Add(formatter);
@ -1397,9 +1438,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
options.OutputFormatters.Add(formatter);
}
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Value)
.Returns(options);
var optionsAccessor = Options.Create(options);
var constraintResolver = new Mock<IInlineConstraintResolver>();
constraintResolver.Setup(c => c.ResolveConstraint("int"))
@ -1408,7 +1447,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var provider = new DefaultApiDescriptionProvider(
optionsAccessor.Object,
optionsAccessor,
constraintResolver.Object,
modelMetadataProvider);
@ -1917,4 +1956,4 @@ namespace Microsoft.AspNetCore.Mvc.Description
public BindingSource BindingSource => BindingSource.FormFile;
}
}
}
}

View File

@ -501,11 +501,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
InvalidModelStateResponseFactory = _ => null,
};
var optionsProvider = Options.Create(options);
var optionsAccessor = Options.Create(options);
modelMetadataProvider = modelMetadataProvider ?? new TestModelMetadataProvider();
var loggerFactory = NullLoggerFactory.Instance;
return new ApiBehaviorApplicationModelProvider(optionsProvider, modelMetadataProvider, loggerFactory);
return new ApiBehaviorApplicationModelProvider(optionsAccessor, modelMetadataProvider, loggerFactory);
}
private static ApplicationModelProviderContext GetContext(Type type)

View File

@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -103,6 +104,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new[] { controllerContext.ActionDescriptor });
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var mvcOptions = Options.Create(new MvcOptions
{
AllowValidatingTopLevelNodes = true,
});
return new ControllerActionInvokerCache(
descriptorProvider,
@ -110,11 +115,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
mvcOptions,
NullLoggerFactory.Instance),
modelBinderFactory,
modelMetadataProvider,
filterProviders,
Mock.Of<IControllerFactoryProvider>());
Mock.Of<IControllerFactoryProvider>(),
mvcOptions);
}
private static ControllerContext CreateControllerContext(FilterDescriptor[] filterDescriptors)

View File

@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -27,8 +28,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ControllerBinderDelegateProviderTest
{
private static readonly MvcOptions _options = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(_options);
[Fact]
public async Task BindActionArgumentsAsync_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnParameter()
public async Task CreateBinderDelegate_Delegate_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnParameter()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -61,6 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -68,7 +76,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -85,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnProperty()
public async Task CreateBinderDelegate_Delegate_DoesNotAddActionArgumentsOrCallBinderOrValidator_IfBindingIsNotAllowed_OnProperty()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -109,12 +118,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var controller = new TestController();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -122,7 +133,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -139,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsNull()
public async Task CreateBinderDelegate_Delegate_DoesNotAddActionArguments_IfBinderReturnsNull()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -155,6 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
binder
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
@ -163,6 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
@ -174,7 +188,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -183,7 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotAddActionArguments_IfBinderDoesNotSetModel()
public async Task CreateBinderDelegate_Delegate_DoesNotAddActionArguments_IfBinderDoesNotSetModel()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -199,6 +214,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
binder
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
@ -207,6 +223,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
@ -218,7 +235,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -227,7 +245,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_AddsActionArguments_IfBinderReturnsNotNull()
public async Task CreateBinderDelegate_Delegate_AddsActionArguments_IfBinderReturnsNotNull()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -251,6 +269,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
context.Result = ModelBindingResult.Success(value);
})
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
@ -259,6 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
@ -270,7 +290,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -280,7 +301,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_GetsMetadataFromParameter()
public async Task CreateBinderDelegate_Delegate_GetsMetadataFromParameter()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -307,12 +328,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
mockMetadataProvider
.Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo))
.Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder(
mockMetadataProvider.Object,
factory,
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -320,7 +343,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
mockMetadataProvider.Object,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -332,7 +356,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_GetsMetadataFromType_IsMetadataProviderIsNotDefaultMetadataProvider()
public async Task CreateBinderDelegate_Delegate_GetsMetadataFromType_IsMetadataProviderIsNotDefaultMetadataProvider()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -357,12 +381,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
mockMetadataProvider
.Setup(p => p.GetMetadataForType(typeof(Person)))
.Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder(
mockMetadataProvider.Object,
factory,
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -370,7 +396,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
mockMetadataProvider.Object,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -382,7 +409,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_CallsValidator_IfModelBinderSucceeds()
public async Task CreateBinderDelegate_Delegate_CallsValidator_IfModelBinderSucceeds()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -410,6 +437,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -419,15 +447,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
// Assert
mockValidator
.Verify(o => o.Validate(
It.IsAny<ModelValidationContext>()),
Times.Once());
mockValidator.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Once());
Assert.False(controllerContext.ModelState.IsValid);
Assert.Equal(
@ -436,7 +462,58 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotCallValidator_IfModelBinderFails()
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,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { 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()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -467,6 +544,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -474,7 +552,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -486,7 +565,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_CallsValidator_ForControllerProperties_IfModelBinderSucceeds()
public async Task CreateBinderDelegate_Delegate_CallsValidator_ForControllerProperties_IfModelBinderSucceeds()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -508,13 +587,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory("Hello");
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -522,15 +601,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
// Assert
mockValidator
.Verify(o => o.Validate(
It.IsAny<ModelValidationContext>()),
Times.Once());
mockValidator.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Once());
Assert.False(controllerContext.ModelState.IsValid);
Assert.Equal(
"some message",
@ -567,6 +644,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
modelMetadataProvider,
factory,
mockValidator.Object,
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -574,7 +652,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -583,7 +662,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotCallValidator_ForControllerProperties_IfModelBinderFails()
public async Task CreateBinderDelegate_Delegate_DoesNotCallValidator_ForControllerProperties_IfModelBinderFails()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -613,6 +692,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -620,7 +700,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -632,7 +713,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_SetsControllerProperties_ForReferenceTypes()
public async Task CreateBinderDelegate_Delegate_SetsControllerProperties_ForReferenceTypes()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -656,6 +737,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -663,7 +745,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -674,7 +757,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_AddsToCollectionControllerProperties()
public async Task CreateBinderDelegate_Delegate_AddsToCollectionControllerProperties()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -699,6 +782,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -706,7 +790,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -717,7 +802,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotSetNullValues_ForNonNullableProperties()
public async Task CreateBinderDelegate_Delegate_DoesNotSetNullValues_ForNonNullableProperties()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -742,6 +827,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value.
@ -752,7 +838,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -761,7 +848,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_SetsNullValues_ForNullableProperties()
public async Task CreateBinderDelegate_Delegate_SetsNullValues_ForNullableProperties()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -786,6 +873,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value.
@ -796,7 +884,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -805,7 +894,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_SupportsRequestPredicate_ForPropertiesAndParameters_NotBound()
public async Task CreateBinderDelegate_Delegate_SupportsRequestPredicate_ForPropertiesAndParameters_NotBound()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -851,6 +940,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value.
@ -861,7 +951,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -871,7 +962,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_SupportsRequestPredicate_ForPropertiesAndParameters_Bound()
public async Task CreateBinderDelegate_Delegate_SupportsRequestPredicate_ForPropertiesAndParameters_Bound()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
@ -917,6 +1008,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value.
@ -927,7 +1019,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -978,7 +1071,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
[Theory]
[MemberData(nameof(SkippedPropertyData))]
public async Task BindActionArgumentsAsync_SkipsReadOnlyControllerProperties(
public async Task CreateBinderDelegate_Delegate_SkipsReadOnlyControllerProperties(
string propertyName,
Type propertyType,
Func<object, object> propertyAccessor,
@ -1007,6 +1100,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -1014,7 +1108,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -1025,7 +1120,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task BindActionArgumentsAsync_SetsMultipleControllerProperties()
public async Task CreateBinderDelegate_Delegate_SetsMultipleControllerProperties()
{
// Arrange
var boundPropertyTypes = new Dictionary<string, Type>
@ -1085,6 +1180,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -1092,7 +1188,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, controller, arguments);
@ -1182,6 +1279,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new EmptyModelMetadataProvider(),
factory,
new DefaultObjectValidator(modelMetadataProvider, new[] { modelValidatorProvider }),
_optionsAccessor,
NullLoggerFactory.Instance);
parameterBinder.Setup(p => p.BindModelAsync(
It.IsAny<ActionContext>(),
@ -1223,7 +1321,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
parameterBinder.Object,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
actionDescriptor);
actionDescriptor,
_options);
await binderDelegate(controllerContext, new TestController(), arguments);
@ -1340,10 +1439,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var objectModelValidator = new DefaultObjectValidator(
metadataProvider,
new[] { modelValidatorProvider });
return new ParameterBinder(
metadataProvider,
factory,
objectModelValidator,
_optionsAccessor,
NullLoggerFactory.Instance);
}

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public async Task MiddlewareFilter_SetsMiddlewareFilterFeature_OnExecution()
{
// Arrange
RequestDelegate requestDelegate = (context) => Task.FromResult(true);
Task requestDelegate(HttpContext context) => Task.FromResult(true);
var middlwareFilter = new MiddlewareFilter(requestDelegate);
var httpContext = new DefaultHttpContext();
var resourceExecutingContext = GetResourceExecutingContext(httpContext);
@ -315,8 +315,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var services = CreateServices();
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = services.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = services.BuildServiceProvider()
};
return httpContext;
}
@ -326,8 +328,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var applicationServices = new ServiceCollection();
var applicationBuilder = new ApplicationBuilder(applicationServices.BuildServiceProvider());
var middlewareFilterBuilderService = new MiddlewareFilterBuilder(
new MiddlewareFilterConfigurationProvider());
middlewareFilterBuilderService.ApplicationBuilder = applicationBuilder;
new MiddlewareFilterConfigurationProvider())
{
ApplicationBuilder = applicationBuilder
};
return middlewareFilterBuilderService.GetPipeline(middlewarePipelineProviderType);
}
@ -444,11 +449,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private class TestParameterBinder : ParameterBinder
{
private readonly IDictionary<string, object> _actionParameters;
public TestParameterBinder(IDictionary<string, object> actionParameters)
: base(
new EmptyModelMetadataProvider(),
TestModelBinderFactory.CreateDefault(),
Mock.Of<IObjectModelValidator>(),
Options.Create(new MvcOptions
{
AllowValidatingTopLevelNodes = true,
}),
NullLoggerFactory.Instance)
{
_actionParameters = actionParameters;

View File

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -22,6 +23,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
public class ParameterBinderTest
{
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(new MvcOptions
{
AllowValidatingTopLevelNodes = true,
});
public static TheoryData BindModelAsyncData
{
get
@ -98,7 +104,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var parameterBinder = new ParameterBinder(
metadataProvider,
factory.Object,
Mock.Of< IObjectModelValidator>(),
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext();
@ -150,6 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider,
factory.Object,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
var valueProvider = new SimpleValueProvider
@ -175,7 +183,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
mockModelMetadata.Setup(o => o.IsBindingRequired).Returns(true);
mockModelMetadata.Setup(o => o.DisplayName).Returns("Ignored Display Name"); // Bind attribute errors are phrased in terms of the model name, not display name
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator: null);
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object);
var modelBindingResult = ModelBindingResult.Failed();
// Act
@ -195,6 +203,37 @@ 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()
{
@ -213,9 +252,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
new RequiredAttribute(),
stringLocalizer: null);
var parameterBinder = CreateParameterBinder(
mockModelMetadata.Object,
validator);
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator);
var modelBindingResult = ModelBindingResult.Success(null);
// Act
@ -235,6 +272,43 @@ 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);
}
[Fact]
public async Task BindModelAsync_EnforcesTopLevelDataAnnotationsAttribute()
{
@ -252,9 +326,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
validationAttribute,
stringLocalizer: null);
var parameterBinder = CreateParameterBinder(
mockModelMetadata.Object,
validator);
var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator);
var modelBindingResult = ModelBindingResult.Success(123);
// Act
@ -353,7 +425,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
private static ParameterBinder CreateParameterBinder(
ModelMetadata modelMetadata,
IModelValidator validator)
IModelValidator validator = null,
IOptions<MvcOptions> optionsAccessor = null)
{
var mockModelMetadataProvider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
mockModelMetadataProvider
@ -361,12 +434,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
.Returns(modelMetadata);
var mockModelBinderFactory = new Mock<IModelBinderFactory>(MockBehavior.Strict);
optionsAccessor = optionsAccessor ?? _optionsAccessor;
return new ParameterBinder(
mockModelMetadataProvider.Object,
mockModelBinderFactory.Object,
new DefaultObjectValidator(
mockModelMetadataProvider.Object,
mockModelMetadataProvider.Object,
new[] { GetModelValidatorProvider(validator) }),
optionsAccessor,
NullLoggerFactory.Instance);
}
@ -390,7 +465,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
});
return validatorProvider.Object;
}
private static ParameterBinder CreateBackCompatParameterBinder(
ModelMetadata modelMetadata,
IObjectModelValidator validator)

View File

@ -1,43 +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;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using SecurityWebSite;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class CombineAuthorizeTests : IClassFixture<MvcTestFixture<StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters>>
{
public CombineAuthorizeTests(MvcTestFixture<StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters> fixture)
{
Client = fixture.CreateDefaultClient();
}
public HttpClient Client { get; }
[Fact]
public async Task CanAuthorizeWithOnlyCookie2()
{
// Arrange & Act
var response = await Client.PostAsync("http://localhost/Administration/SignInCookie2", null);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(response.Headers.Contains("Set-Cookie"));
var cookie2 = response.Headers.GetValues("Set-Cookie").SingleOrDefault();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Administration/EitherCookie");
request.Headers.Add("Cookie", cookie2);
response = await Client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Administration.EitherCookie:AuthorizeCount=1", body);
}
}
}

View File

@ -6,7 +6,9 @@ using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
@ -15,8 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public GlobalAuthorizationFilterTest(MvcTestFixture<SecurityWebSite.StartupWithGlobalDenyAnonymousFilter> fixture)
{
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = factory.CreateDefaultClient();
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = Factory.CreateDefaultClient();
}
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
@ -24,6 +26,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public HttpClient Client { get; }
public WebApplicationFactory<SecurityWebSite.StartupWithGlobalDenyAnonymousFilter> Factory { get; }
[Fact]
public async Task DeniesAnonymousUsers_ByDefault()
{
@ -51,10 +55,15 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
}
[Fact]
public async Task AuthorizationPoliciesDoNotCombineByDefault()
public async Task AuthorizationPoliciesDoNotCombine_WithV2_0()
{
// Arrange & Act
var response = await Client.PostAsync("http://localhost/Administration/SignInCookie2", null);
var factory = Factory.WithWebHostBuilder(
builder => builder.ConfigureServices(
services => services.Configure<MvcCompatibilityOptions>(
options => options.CompatibilityVersion = CompatibilityVersion.Version_2_0)));
var client = factory.CreateDefaultClient();
var response = await client.PostAsync("http://localhost/Administration/SignInCookie2", null);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@ -65,8 +74,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Administration/EitherCookie");
request.Headers.Add("Cookie", cookie2);
// Will fail because default cookie is not sent so [Authorize] fails
response = await Client.SendAsync(request);
// Will fail because default cookie is not sent so [Authorize] fails.
response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.NotNull(response.Headers.Location);
Assert.Equal(
@ -74,5 +83,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
response.Headers.Location.ToString());
}
}
}
[Fact]
public async Task AuthorizationPoliciesCombine_WithV2_1()
{
// Arrange & Act 1
var response = await Client.PostAsync("http://localhost/Administration/SignInCookie2", null);
// Assert 1
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(response.Headers.Contains("Set-Cookie"));
// Arrange 2
var cookie2 = response.Headers.GetValues("Set-Cookie").SingleOrDefault();
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.
response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Null(response.Headers.Location);
}
}
}

View File

@ -1,20 +1,26 @@
// 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.Globalization;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class MvcTestFixture<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder.UseRequestCulture<TStartup>("en-GB", "en-US");
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseRequestCulture<TStartup>("en-GB", "en-US")
.ConfigureServices(
services => services.Configure<MvcCompatibilityOptions>(
options => options.CompatibilityVersion = CompatibilityVersion.Version_2_1));
}
protected override TestServer CreateServer(IWebHostBuilder builder)
{

View File

@ -109,9 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
// Update me in 3.0 xD
Assert.Equal("{\"\":[\"The input was not valid.\"]}", responseBody);
Assert.Equal("{\"\":[\"Unexpected end when reading JSON. Path '', line 1, position 1.\"]}", responseBody);
}
[Theory]

View File

@ -384,7 +384,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = true;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
// Act
@ -428,7 +428,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = true;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
// Act
@ -453,7 +453,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_JsonFormatterAddsModelStateError()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
// Override the AllowInputFormatterExceptionMessages setting ModelBindingTestHelper chooses.
var options = testContext.GetService<IOptions<MvcJsonOptions>>().Value;
options.AllowInputFormatterExceptionMessages = false;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
@ -464,13 +475,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Person4)
};
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
var modelState = testContext.ModelState;
// Act
@ -486,7 +490,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Null(entry.Value.AttemptedValue);
Assert.Null(entry.Value.RawValue);
var error = Assert.Single(entry.Value.Errors);
// Update me in 3.0 when MvcJsonOptions.AllowInputFormatterExceptionMessages is removed
Assert.IsType<JsonSerializationException>(error.Exception);
Assert.Empty(error.ErrorMessage);
@ -549,7 +553,18 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public async Task FromBodyWithInvalidPropertyData_JsonFormatterAddsModelError()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Number\": \"not a number\" }"));
request.ContentType = "application/json";
});
// Override the AllowInputFormatterExceptionMessages setting ModelBindingTestHelper chooses.
var options = testContext.GetService<IOptions<MvcJsonOptions>>().Value;
options.AllowInputFormatterExceptionMessages = false;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
@ -560,13 +575,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Person5)
};
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ \"Number\": \"not a number\" }"));
request.ContentType = "application/json";
});
var modelState = testContext.ModelState;
// Act
@ -596,7 +604,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData(false, false)]
[InlineData(true, true)]
public async Task FromBodyWithEmptyBody_JsonFormatterAddsModelErrorWhenExpected(
bool allowEmptyInputInBodyModelBindingSetting,
bool allowEmptyInputInBodyModelBindingSetting,
bool expectedModelStateIsValid)
{
// Arrange
@ -615,10 +623,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
},
options => options.AllowEmptyInputInBodyModelBinding = allowEmptyInputInBodyModelBindingSetting);
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = allowEmptyInputInBodyModelBindingSetting;
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
@ -630,7 +638,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var boundPerson = Assert.IsType<Person5>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
if (expectedModelStateIsValid)
{
Assert.True(modelState.IsValid);

View File

@ -2701,7 +2701,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
entry = Assert.Single(modelState, e => e.Key == "Info.Value.GpsCoordinates").Value;
Assert.Equal("10,20", entry.AttemptedValue);
Assert.Equal(new[] { "10", "20" }, entry.RawValue);
Assert.Equal("10,20", entry.RawValue);
}
private static void SetJsonBodyContent(HttpRequest request, string content)

View File

@ -441,14 +441,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Action<HttpRequest> updateRequest = null,
Action<MvcOptions> updateOptions = null)
{
if (updateOptions == null)
{
updateOptions = o =>
{
o.AllowBindingHeaderValuesToNonStringModelTypes = true;
};
}
return ModelBindingTestHelper.GetTestContext(updateRequest, updateOptions);
}
@ -520,4 +512,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
}
}
}

View File

@ -71,6 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
new DefaultObjectValidator(
metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }),
options,
NullLoggerFactory.Instance);
}
@ -95,6 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
new DefaultObjectValidator(
metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }),
options,
NullLoggerFactory.Instance);
}
@ -151,7 +153,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var serviceCollection = new ServiceCollection();
serviceCollection.AddAuthorization();
serviceCollection.AddSingleton(new ApplicationPartManager());
serviceCollection.AddMvc();
serviceCollection
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
serviceCollection
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
.AddTransient<ILoggerFactory, LoggerFactory>()

View File

@ -487,10 +487,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var mvcOptions = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
TestModelBinderFactory.CreateDefault(),
Mock.Of<IObjectModelValidator>(),
Options.Create(mvcOptions),
NullLoggerFactory.Instance);
return new PageActionInvokerProvider(

View File

@ -26,6 +26,7 @@ using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -1240,10 +1241,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var mvcOptions = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
return new ParameterBinder(
metadataProvider,
factory,
new DefaultObjectValidator(metadataProvider, new[] { validator }),
Options.Create(mvcOptions),
NullLoggerFactory.Instance);
}

View File

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
@ -22,6 +23,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageBinderFactoryTest
{
private static readonly MvcOptions _options = new MvcOptions
{
AllowValidatingTopLevelNodes = true,
};
private static readonly IOptions<MvcOptions> _optionsAccessor = Options.Create(_options);
[Fact]
public void GetModelBinderFactory_ReturnsNullIfPageHasNoBoundProperties()
{
@ -37,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -62,6 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -86,6 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -111,6 +121,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -135,6 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -160,6 +172,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act
@ -285,7 +298,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
// Verify that the page properties were not bound.
Assert.Equal(default(int), page.Id);
Assert.Equal(default, page.Id);
Assert.Null(page.RouteDifferentValue);
Assert.Equal(10, model.Id);
@ -541,6 +554,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
new DefaultObjectValidator(
modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
_optionsAccessor,
NullLoggerFactory.Instance);
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
@ -581,13 +595,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var factory = PageBinderFactory.CreateHandlerBinder(
parameterBinder,
modelMetadataProvider,
modelBinderFactory,
actionDescriptor,
actionDescriptor.HandlerMethods[0]);
actionDescriptor.HandlerMethods[0],
_options);
var page = new PageWithProperty
{
@ -625,13 +639,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var factory = PageBinderFactory.CreateHandlerBinder(
parameterBinder,
modelMetadataProvider,
modelBinderFactory,
actionDescriptor,
actionDescriptor.HandlerMethods[0]);
actionDescriptor.HandlerMethods[0],
_options);
var page = new PageWithProperty
{
@ -659,13 +673,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
_optionsAccessor,
NullLoggerFactory.Instance);
var factory = PageBinderFactory.CreateHandlerBinder(
@ -673,7 +687,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelMetadataProvider,
modelBinderFactory,
actionDescriptor,
actionDescriptor.HandlerMethods[0]);
actionDescriptor.HandlerMethods[0],
_options);
var page = new PageWithProperty
{
@ -690,13 +705,62 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelState = page.PageContext.ModelState;
Assert.False(modelState.IsValid);
Assert.Collection(
page.PageContext.ModelState,
modelState,
kvp =>
{
Assert.Equal("name", kvp.Key);
});
}
[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() }),
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);
@ -756,6 +820,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
TestModelMetadataProvider.CreateDefaultProvider(),
TestModelBinderFactory.CreateDefault(),
Mock.Of<IObjectModelValidator>(),
_optionsAccessor,
NullLoggerFactory.Instance)
{
_args = args;
@ -914,8 +979,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void OnPostWithValidation([Required] string name)
{
}
}
}
}

View File

@ -25,8 +25,7 @@ namespace RazorPagesWebSite
options.Conventions.AddPageRoute("/Pages/NotTheRoot", string.Empty);
options.Conventions.Add(new CustomModelTypeConvention());
})
.WithRazorPagesAtContentRoot()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
.WithRazorPagesAtContentRoot();
}
public void Configure(IApplicationBuilder app)

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesWebSite.Conventions;
@ -24,8 +24,7 @@ namespace RazorPagesWebSite
options.Conventions.AuthorizeAreaFolder("Accounts", "/RequiresAuth");
options.Conventions.AllowAnonymousToAreaPage("Accounts", "/RequiresAuth/AllowAnonymous");
options.Conventions.Add(new CustomModelTypeConvention());
})
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
});
}
public void Configure(IApplicationBuilder app)

View File

@ -1,47 +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.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace SecurityWebSite
{
public class StartupWithGlobalAuthorizeAndAllowCombiningAuthorizeFilters
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(o =>
{
o.AllowCombiningAuthorizeFilters = true;
o.Filters.Add(new AuthorizeFilter());
});
services.AddAntiforgery();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/Home/Login";
options.LogoutPath = "/Home/Logout";
}).AddCookie("Cookie2");
services.AddScoped<IPolicyEvaluator, CountingPolicyEvaluator>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}