Merge remote-tracking branch 'origin/release/2.2'

# Conflicts:
#	build/dependencies.props
#	korebuild-lock.txt
This commit is contained in:
Pranav K 2018-08-16 15:13:24 -07:00
commit 3c6ae4542e
No known key found for this signature in database
GPG Key ID: 1963DA6D96C3057A
67 changed files with 1451 additions and 202 deletions

View File

@ -3,8 +3,6 @@ ASP.NET Core MVC
**Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack** **Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack**
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/969jbosi0qwc1awg/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/mvc/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=dev)](https://travis-ci.org/aspnet/Mvc) Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=dev)](https://travis-ci.org/aspnet/Mvc)
ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards. ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc
string Action(UrlActionContext actionContext); string Action(UrlActionContext actionContext);
/// <summary> /// <summary>
/// Converts a virtual (relative) path to an application absolute path. /// Converts a virtual (relative, starting with ~/) path to an application absolute path.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If the specified content path does not start with the tilde (~) character, /// If the specified content path does not start with the tilde (~) character,

View File

@ -42,5 +42,15 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
"Usage", "Usage",
DiagnosticSeverity.Warning, DiagnosticSeverity.Warning,
isEnabledByDefault: true); isEnabledByDefault: true);
public static readonly DiagnosticDescriptor MVC1004_ParameterNameCollidesWithTopLevelProperty =
new DiagnosticDescriptor(
"MVC1004",
"Rename model bound parameter.",
"Property on type '{0}' has the same name as parameter '{1}'. This may result in incorrect model binding. Consider renaming the parameter or using a model binding attribute to override the name.",
"Naming",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/AA20pbc");
} }
} }

View File

@ -5,7 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers namespace Microsoft.AspNetCore.Mvc.Analyzers
{ {
internal static class MvcFacts internal static class MvcFacts
{ {

View File

@ -19,14 +19,20 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute";
public const string BindAttribute = "Microsoft.AspNetCore.Mvc.BindAttribute";
public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute";
public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute";
public const string FromBodyAttribute = "Microsoft.AspNetCore.Mvc.FromBodyAttribute";
public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions";
public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata";
public const string IBinderTypeProviderMetadata = "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata";
public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult";
public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult"; public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult";
@ -35,6 +41,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper";
public const string IModelNameProvider = "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider";
public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider";
public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary";

View File

@ -0,0 +1,215 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Mvc.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class TopLevelParameterNameAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(compilationStartAnalysisContext =>
{
var typeCache = new SymbolCache(compilationStartAnalysisContext.Compilation);
if (typeCache.ControllerAttribute == null || typeCache.ControllerAttribute.TypeKind == TypeKind.Error)
{
// No-op if we can't find types we care about.
return;
}
InitializeWorker(compilationStartAnalysisContext, typeCache);
});
}
private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, SymbolCache symbolCache)
{
compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext =>
{
var method = (IMethodSymbol)symbolAnalysisContext.Symbol;
if (method.MethodKind != MethodKind.Ordinary)
{
return;
}
if (method.Parameters.Length == 0)
{
return;
}
if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute) ||
!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose))
{
return;
}
if (method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true))
{
// The issue of parameter name collision with properties affects complex model-bound types
// and not input formatting. Ignore ApiController instances since they default to formatting.
return;
}
for (var i = 0; i < method.Parameters.Length; i++)
{
var parameter = method.Parameters[i];
if (IsProblematicParameter(symbolCache, parameter))
{
var location = parameter.Locations.Length != 0 ?
parameter.Locations[0] :
Location.None;
symbolAnalysisContext.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty,
location,
parameter.Type.Name,
parameter.Name));
}
}
}, SymbolKind.Method);
}
internal static bool IsProblematicParameter(in SymbolCache symbolCache, IParameterSymbol parameter)
{
if (parameter.GetAttributes(symbolCache.FromBodyAttribute).Any())
{
// Ignore input formatted parameters.
return false;
}
if (SpecifiesModelType(symbolCache, parameter))
{
// Ignore parameters that specify a model type.
return false;
}
var parameterName = GetName(symbolCache, parameter);
var type = parameter.Type;
while (type != null)
{
foreach (var member in type.GetMembers())
{
if (member.DeclaredAccessibility != Accessibility.Public ||
member.IsStatic ||
member.Kind != SymbolKind.Property)
{
continue;
}
var propertyName = GetName(symbolCache, member);
if (string.Equals(parameterName, propertyName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
type = type.BaseType;
}
return false;
}
internal static string GetName(in SymbolCache symbolCache, ISymbol symbol)
{
foreach (var attribute in symbol.GetAttributes(symbolCache.IModelNameProvider))
{
// BindAttribute uses the Prefix property as an alias for IModelNameProvider.Name
var nameProperty = attribute.AttributeClass == symbolCache.BindAttribute ? "Prefix" : "Name";
// All of the built-in attributes (FromQueryAttribute, ModelBinderAttribute etc) only support setting the name via
// a property. We'll ignore constructor values.
for (var i = 0; i < attribute.NamedArguments.Length; i++)
{
var namedArgument = attribute.NamedArguments[i];
var namedArgumentValue = namedArgument.Value;
if (string.Equals(namedArgument.Key, nameProperty, StringComparison.Ordinal) &&
namedArgumentValue.Kind == TypedConstantKind.Primitive &&
namedArgumentValue.Type.SpecialType == SpecialType.System_String &&
namedArgumentValue.Value is string name)
{
return name;
}
}
}
return symbol.Name;
}
internal static bool SpecifiesModelType(in SymbolCache symbolCache, IParameterSymbol parameterSymbol)
{
foreach (var attribute in parameterSymbol.GetAttributes(symbolCache.IBinderTypeProviderMetadata))
{
// Look for a attribute property named BinderType being assigned. This would match
// [ModelBinder(BinderType = typeof(SomeBinder))]
for (var i = 0; i < attribute.NamedArguments.Length; i++)
{
var namedArgument = attribute.NamedArguments[i];
var namedArgumentValue = namedArgument.Value;
if (string.Equals(namedArgument.Key, "BinderType", StringComparison.Ordinal) &&
namedArgumentValue.Kind == TypedConstantKind.Type)
{
return true;
}
}
// Look for the binder type being specified in the constructor. This would match
// [ModelBinder(typeof(SomeBinder))]
var constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
for (var i = 0; i < constructorParameters.Length; i++)
{
if (string.Equals(constructorParameters[i].Name, "binderType", StringComparison.Ordinal))
{
// A constructor that requires binderType was used.
return true;
}
}
}
return false;
}
internal readonly struct SymbolCache
{
public SymbolCache(Compilation compilation)
{
BindAttribute = compilation.GetTypeByMetadataName(SymbolNames.BindAttribute);
ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute);
FromBodyAttribute = compilation.GetTypeByMetadataName(SymbolNames.FromBodyAttribute);
IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata);
IBinderTypeProviderMetadata = compilation.GetTypeByMetadataName(SymbolNames.IBinderTypeProviderMetadata);
IModelNameProvider = compilation.GetTypeByMetadataName(SymbolNames.IModelNameProvider);
NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute);
NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute);
var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable);
var members = disposable.GetMembers(nameof(IDisposable.Dispose));
IDisposableDispose = members.Length == 1 ? (IMethodSymbol)members[0] : null;
}
public INamedTypeSymbol BindAttribute { get; }
public INamedTypeSymbol ControllerAttribute { get; }
public INamedTypeSymbol FromBodyAttribute { get; }
public INamedTypeSymbol IApiBehaviorMetadata { get; }
public INamedTypeSymbol IBinderTypeProviderMetadata { get; }
public INamedTypeSymbol IModelNameProvider { get; }
public INamedTypeSymbol NonControllerAttribute { get; }
public INamedTypeSymbol NonActionAttribute { get; }
public IMethodSymbol IDisposableDispose { get; }
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Analyzers;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers namespace Microsoft.AspNetCore.Mvc.Api.Analyzers

View File

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="..\Microsoft.AspNetCore.Mvc.Analyzers\CodeAnalysisExtensions.cs" /> <Compile Include="..\Microsoft.AspNetCore.Mvc.Analyzers\CodeAnalysisExtensions.cs" />
<Compile Include="..\Microsoft.AspNetCore.Mvc.Analyzers\MvcFacts.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
public class ContentResult : ActionResult public class ContentResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Gets or set the content representing the body of the response. /// Gets or set the content representing the body of the response.

View File

@ -2,26 +2,33 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Internal namespace Microsoft.AspNetCore.Mvc
{ {
/// <summary> /// <summary>
/// The default implementation of <see cref="IObjectModelValidator"/>. /// The default implementation of <see cref="IObjectModelValidator"/>.
/// </summary> /// </summary>
public class DefaultObjectValidator : ObjectModelValidator internal class DefaultObjectValidator : ObjectModelValidator
{ {
private readonly MvcOptions _mvcOptions;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="DefaultObjectValidator"/>. /// Initializes a new instance of <see cref="DefaultObjectValidator"/>.
/// </summary> /// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="validatorProviders">The list of <see cref="IModelValidatorProvider"/>.</param> /// <param name="validatorProviders">The list of <see cref="IModelValidatorProvider"/>.</param>
/// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param>
public DefaultObjectValidator( public DefaultObjectValidator(
IModelMetadataProvider modelMetadataProvider, IModelMetadataProvider modelMetadataProvider,
IList<IModelValidatorProvider> validatorProviders) IList<IModelValidatorProvider> validatorProviders,
MvcOptions mvcOptions)
: base(modelMetadataProvider, validatorProviders) : base(modelMetadataProvider, validatorProviders)
{ {
_mvcOptions = mvcOptions;
} }
public override ValidationVisitor GetValidationVisitor( public override ValidationVisitor GetValidationVisitor(
@ -31,12 +38,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IModelMetadataProvider metadataProvider, IModelMetadataProvider metadataProvider,
ValidationStateDictionary validationState) ValidationStateDictionary validationState)
{ {
return new ValidationVisitor( var visitor = new ValidationVisitor(
actionContext, actionContext,
validatorProvider, validatorProvider,
validatorCache, validatorCache,
metadataProvider, metadataProvider,
validationState); validationState);
visitor.MaxValidationDepth = _mvcOptions.MaxValidationDepth;
return visitor;
} }
} }
} }

View File

@ -228,7 +228,7 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value; var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>(); var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders); return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders, options);
}); });
services.TryAddSingleton<ClientValidatorCache>(); services.TryAddSingleton<ClientValidatorCache>();
services.TryAddSingleton<ParameterBinder>(); services.TryAddSingleton<ParameterBinder>();

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
/// <summary>
/// Represents an <see cref="IActionResult"/> that when executed will
/// produce an HTTP response with the specified <see cref="StatusCode"/>.
/// </summary>
public interface IStatusCodeActionResult : IActionResult
{
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
int? StatusCode { get; }
}
}

View File

@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
if (Version >= CompatibilityVersion.Version_2_2) if (Version >= CompatibilityVersion.Version_2_2)
{ {
values[nameof(MvcOptions.EnableEndpointRouting)] = true; values[nameof(MvcOptions.EnableEndpointRouting)] = true;
// Matches JsonSerializerSettingsProvider.DefaultMaxDepth
values[nameof(MvcOptions.MaxValidationDepth)] = 32;
} }
return values; return values;

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
internal class NullableCompatibilitySwitch<TValue> : ICompatibilitySwitch where TValue : struct
{
private TValue? _value;
public NullableCompatibilitySwitch(string name)
{
Name = name;
}
public bool IsValueSet { get; private set; }
public string Name { get; }
public TValue? Value
{
get => _value;
set
{
IsValueSet = true;
_value = value;
}
}
object ICompatibilitySwitch.Value
{
get => Value;
set => Value = (TValue?)value;
}
}
}

View File

@ -4,8 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{ {
@ -15,6 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
/// </summary> /// </summary>
public class ValidationVisitor public class ValidationVisitor
{ {
private int? _maxValidationDepth;
/// <summary> /// <summary>
/// Creates a new <see cref="ValidationVisitor"/>. /// Creates a new <see cref="ValidationVisitor"/>.
/// </summary> /// </summary>
@ -76,6 +80,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
protected ModelMetadata Metadata { get; set; } protected ModelMetadata Metadata { get; set; }
protected IValidationStrategy Strategy { get; set; } protected IValidationStrategy Strategy { get; set; }
/// <summary>
/// Gets or sets the maximum depth to constrain the validation visitor when validating.
/// <para>
/// <see cref="ValidationVisitor"/> traverses the object graph of the model being validated. For models
/// that are very deep or are infinitely recursive, validation may result in stack overflow.
/// </para>
/// <para>
/// When not <see langword="null"/>, <see cref="Visit(ModelMetadata, string, object)"/> will throw if
/// current traversal depth exceeds the specified value.
/// </para>
/// </summary>
public int? MaxValidationDepth
{
get => _maxValidationDepth;
set
{
if (value != null && value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_maxValidationDepth = value;
}
}
/// <summary> /// <summary>
/// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false. /// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false.
/// </summary> /// </summary>
@ -192,6 +221,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
return true; return true;
} }
if (MaxValidationDepth != null && CurrentPath.Count > MaxValidationDepth)
{
// Non cyclic but too deep an object graph.
// Pop the current model to make ValidationStack.Dispose happy
CurrentPath.Pop(model);
string message;
switch (metadata.MetadataKind)
{
case ModelMetadataKind.Property:
message = Resources.FormatValidationVisitor_ExceededMaxPropertyDepth(nameof(ValidationVisitor), MaxValidationDepth, metadata.Name, metadata.ContainerType);
break;
default:
// Since the minimum depth is never 0, MetadataKind can never be Parameter. Consequently we only special case MetadataKind.Property.
message = Resources.FormatValidationVisitor_ExceededMaxDepth(nameof(ValidationVisitor), MaxValidationDepth, metadata.ModelType);
break;
}
message += " " + Resources.FormatValidationVisitor_ExceededMaxDepthFix(nameof(MvcOptions), nameof(MvcOptions.MaxValidationDepth));
throw new InvalidOperationException(message)
{
HelpLink = "https://aka.ms/AA21ue1",
};
}
var entry = GetValidationEntry(model); var entry = GetValidationEntry(model);
key = entry?.Key ?? key ?? string.Empty; key = entry?.Key ?? key ?? string.Empty;
metadata = entry?.Metadata ?? metadata; metadata = entry?.Metadata ?? metadata;

View File

@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc
private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch<InputFormatterExceptionPolicy> _inputFormatterExceptionPolicy;
private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch<bool> _suppressBindingUndefinedValueToEnumType;
private readonly CompatibilitySwitch<bool> _enableEndpointRouting; private readonly CompatibilitySwitch<bool> _enableEndpointRouting;
private readonly NullableCompatibilitySwitch<int> _maxValidationDepth;
private readonly ICompatibilitySwitch[] _switches; private readonly ICompatibilitySwitch[] _switches;
/// <summary> /// <summary>
@ -56,6 +57,7 @@ namespace Microsoft.AspNetCore.Mvc
_inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _inputFormatterExceptionPolicy = new CompatibilitySwitch<InputFormatterExceptionPolicy>(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions);
_suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType)); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch<bool>(nameof(SuppressBindingUndefinedValueToEnumType));
_enableEndpointRouting = new CompatibilitySwitch<bool>(nameof(EnableEndpointRouting)); _enableEndpointRouting = new CompatibilitySwitch<bool>(nameof(EnableEndpointRouting));
_maxValidationDepth = new NullableCompatibilitySwitch<int>(nameof(MaxValidationDepth));
_switches = new ICompatibilitySwitch[] _switches = new ICompatibilitySwitch[]
{ {
@ -65,6 +67,7 @@ namespace Microsoft.AspNetCore.Mvc
_inputFormatterExceptionPolicy, _inputFormatterExceptionPolicy,
_suppressBindingUndefinedValueToEnumType, _suppressBindingUndefinedValueToEnumType,
_enableEndpointRouting, _enableEndpointRouting,
_maxValidationDepth,
}; };
} }
@ -396,6 +399,49 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public bool RequireHttpsPermanent { get; set; } public bool RequireHttpsPermanent { get; set; }
/// <summary>
/// Gets or sets the maximum depth to constrain the validation visitor when validating. Set to <see langword="null" />
/// to disable this feature.
/// <para>
/// <see cref="ValidationVisitor"/> traverses the object graph of the model being validated. For models
/// that are very deep or are infinitely recursive, validation may result in stack overflow.
/// </para>
/// <para>
/// When not <see langword="null"/>, <see cref="ValidationVisitor"/> will throw if
/// traversing an object exceeds the maximum allowed validation depth.
/// </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
/// 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_2"/> then
/// this setting will have the value <c>200</c> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// earlier then this setting will have the value <see langword="null"/> unless explicitly configured.
/// </para>
/// </summary>
public int? MaxValidationDepth
{
get => _maxValidationDepth.Value;
set
{
if (value != null && value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_maxValidationDepth.Value = value;
}
}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator() IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{ {
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator(); return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
public class ObjectResult : ActionResult public class ObjectResult : ActionResult, IStatusCodeActionResult
{ {
public ObjectResult(object value) public ObjectResult(object value)
{ {

View File

@ -1536,6 +1536,48 @@ namespace Microsoft.AspNetCore.Mvc.Core
internal static string FormatApiConventionMethod_NoMethodFound(object p0, object p1) internal static string FormatApiConventionMethod_NoMethodFound(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_NoMethodFound"), p0, p1); => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_NoMethodFound"), p0, p1);
/// <summary>
/// {0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'.
/// </summary>
internal static string ValidationVisitor_ExceededMaxDepth
{
get => GetString("ValidationVisitor_ExceededMaxDepth");
}
/// <summary>
/// {0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'.
/// </summary>
internal static string FormatValidationVisitor_ExceededMaxDepth(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxDepth"), p0, p1, p2);
/// <summary>
/// This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type.
/// </summary>
internal static string ValidationVisitor_ExceededMaxDepthFix
{
get => GetString("ValidationVisitor_ExceededMaxDepthFix");
}
/// <summary>
/// This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type.
/// </summary>
internal static string FormatValidationVisitor_ExceededMaxDepthFix(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxDepthFix"), p0, p1);
/// <summary>
/// {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.
/// </summary>
internal static string ValidationVisitor_ExceededMaxPropertyDepth
{
get => GetString("ValidationVisitor_ExceededMaxPropertyDepth");
}
/// <summary>
/// {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.
/// </summary>
internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxPropertyDepth"), p0, p1, p2, p3);
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -457,4 +457,13 @@
<data name="ApiConventionMethod_NoMethodFound" xml:space="preserve"> <data name="ApiConventionMethod_NoMethodFound" xml:space="preserve">
<value>A method named '{0}' was not found on convention type '{1}'.</value> <value>A method named '{0}' was not found on convention type '{1}'.</value>
</data> </data>
<data name="ValidationVisitor_ExceededMaxDepth" xml:space="preserve">
<value>{0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'.</value>
</data>
<data name="ValidationVisitor_ExceededMaxDepthFix" xml:space="preserve">
<value>This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type.</value>
</data>
<data name="ValidationVisitor_ExceededMaxPropertyDepth" xml:space="preserve">
<value>{0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'.</value>
</data>
</root> </root>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
/// Represents an <see cref="ActionResult"/> that when executed will /// Represents an <see cref="ActionResult"/> that when executed will
/// produce an HTTP response with the given response status code. /// produce an HTTP response with the given response status code.
/// </summary> /// </summary>
public class StatusCodeResult : ActionResult public class StatusCodeResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StatusCodeResult"/> class /// Initializes a new instance of the <see cref="StatusCodeResult"/> class
@ -29,6 +30,8 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public int StatusCode { get; } public int StatusCode { get; }
int? IStatusCodeActionResult.StatusCode => StatusCode;
/// <inheritdoc /> /// <inheritdoc />
public override void ExecuteResult(ActionContext context) public override void ExecuteResult(ActionContext context)
{ {

View File

@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc
} }
/// <summary> /// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>. /// Generates a URL with a relative path for the specified <paramref name="pageName"/>.
/// </summary> /// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param> /// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param> /// <param name="pageName">The page name to generate the url for.</param>
@ -347,7 +347,7 @@ namespace Microsoft.AspNetCore.Mvc
=> Page(urlHelper, pageName, values: null); => Page(urlHelper, pageName, values: null);
/// <summary> /// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>. /// Generates a URL with a relative path for the specified <paramref name="pageName"/>.
/// </summary> /// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param> /// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param> /// <param name="pageName">The page name to generate the url for.</param>
@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Mvc
=> Page(urlHelper, pageName, pageHandler, values: null); => Page(urlHelper, pageName, pageHandler, values: null);
/// <summary> /// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>. /// Generates a URL with a relative path for the specified <paramref name="pageName"/>.
/// </summary> /// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param> /// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param> /// <param name="pageName">The page name to generate the url for.</param>
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Mvc
=> Page(urlHelper, pageName, pageHandler: null, values: values); => Page(urlHelper, pageName, pageHandler: null, values: values);
/// <summary> /// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>. /// Generates a URL with a relative path for the specified <paramref name="pageName"/>.
/// </summary> /// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param> /// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param> /// <param name="pageName">The page name to generate the url for.</param>

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// An action result which formats the given object as JSON. /// An action result which formats the given object as JSON.
/// </summary> /// </summary>
public class JsonResult : ActionResult public class JsonResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Creates a new <see cref="JsonResult"/> with the given <paramref name="value"/>. /// Creates a new <see cref="JsonResult"/> with the given <paramref name="value"/>.

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Represents an <see cref="ActionResult"/> that renders a partial view to the response. /// Represents an <see cref="ActionResult"/> that renders a partial view to the response.
/// </summary> /// </summary>
public class PartialViewResult : ActionResult public class PartialViewResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Gets or sets the HTTP status code. /// Gets or sets the HTTP status code.

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// An <see cref="IActionResult"/> which renders a view component to the response. /// An <see cref="IActionResult"/> which renders a view component to the response.
/// </summary> /// </summary>
public class ViewComponentResult : ActionResult public class ViewComponentResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Gets or sets the arguments provided to the view component. /// Gets or sets the arguments provided to the view component.

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Represents an <see cref="ActionResult"/> that renders a view to the response. /// Represents an <see cref="ActionResult"/> that renders a view to the response.
/// </summary> /// </summary>
public class ViewResult : ActionResult public class ViewResult : ActionResult, IStatusCodeActionResult
{ {
/// <summary> /// <summary>
/// Gets or sets the HTTP status code. /// Gets or sets the HTTP status code.

View File

@ -2711,7 +2711,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
controller.ObjectValidator = new DefaultObjectValidator( controller.ObjectValidator = new DefaultObjectValidator(
controller.MetadataProvider, controller.MetadataProvider,
new[] { Mock.Of<IModelValidatorProvider>() }); new[] { Mock.Of<IModelValidatorProvider>() },
new MvcOptions());
var model = new TryValidateModelModel(); var model = new TryValidateModelModel();
@ -2747,7 +2748,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
controller.ObjectValidator = new DefaultObjectValidator( controller.ObjectValidator = new DefaultObjectValidator(
controller.MetadataProvider, controller.MetadataProvider,
new[] { provider.Object }); new[] { provider.Object },
new MvcOptions());
// Act // Act
var result = controller.TryValidateModel(model, "Prefix"); var result = controller.TryValidateModel(model, "Prefix");
@ -2783,7 +2785,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
var controller = GetController(binder, valueProvider: null); var controller = GetController(binder, valueProvider: null);
controller.ObjectValidator = new DefaultObjectValidator( controller.ObjectValidator = new DefaultObjectValidator(
controller.MetadataProvider, controller.MetadataProvider,
new[] { provider.Object }); new[] { provider.Object },
new MvcOptions());
// Act // Act
var result = controller.TryValidateModel(model); var result = controller.TryValidateModel(model);
@ -2867,7 +2870,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
ControllerContext = controllerContext, ControllerContext = controllerContext,
MetadataProvider = metadataProvider, MetadataProvider = metadataProvider,
ModelBinderFactory = binderFactory.Object, ModelBinderFactory = binderFactory.Object,
ObjectValidator = new DefaultObjectValidator(metadataProvider, validatorProviders), ObjectValidator = new DefaultObjectValidator(metadataProvider, validatorProviders, new MvcOptions()),
}; };
return controller; return controller;

View File

@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
.Returns(metadataProvider); .Returns(metadataProvider);
services services
.Setup(s => s.GetService(typeof(IObjectModelValidator))) .Setup(s => s.GetService(typeof(IObjectModelValidator)))
.Returns(new DefaultObjectValidator(metadataProvider, new List<IModelValidatorProvider>())); .Returns(new DefaultObjectValidator(metadataProvider, new List<IModelValidatorProvider>(), new MvcOptions()));
return services.Object; return services.Object;
} }

View File

@ -215,7 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
.Setup(s => s.GetService(typeof(IObjectModelValidator))) .Setup(s => s.GetService(typeof(IObjectModelValidator)))
.Returns(new DefaultObjectValidator( .Returns(new DefaultObjectValidator(
metadataProvider, metadataProvider,
TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders)); TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders,
new MvcOptions()));
return services.Object; return services.Object;
} }

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -32,6 +33,19 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode); Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
} }
[Fact]
public void HttpStatusCodeResult_ReturnsCorrectStatusCodeAsIStatusCodeActionResult()
{
// Arrange
var result = new StatusCodeResult(StatusCodes.Status404NotFound);
// Act
var statusResult = result as IStatusCodeActionResult;
// Assert
Assert.Equal(StatusCodes.Status404NotFound, statusResult?.StatusCode);
}
private static IServiceCollection CreateServices() private static IServiceCollection CreateServices()
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();

View File

@ -0,0 +1,83 @@
// 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_2_2,
};
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_2_2,
};
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_2_2,
};
var configureOptions = new MvcOptionsConfigureCompatibilityOptions(
NullLoggerFactory.Instance,
Options.Create(mvcCompatibilityOptions));
// Act
configureOptions.PostConfigure(string.Empty, mvcOptions);
// Assert
Assert.Equal(expected, mvcOptions.MaxValidationDepth);
}
}
}

View File

@ -0,0 +1,77 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class NullableCompatibilitySwitchTest
{
[Fact]
public void Constructor_WithName_IsValueSetIsFalse()
{
// Arrange & Act
var @switch = new NullableCompatibilitySwitch<bool>("TestProperty");
// Assert
Assert.Null(@switch.Value);
Assert.False(@switch.IsValueSet);
}
[Fact]
public void ValueNonInterface_SettingValueToNull_SetsIsValueSetToTrue()
{
// Arrange
var @switch = new NullableCompatibilitySwitch<bool>("TestProperty");
// Act
@switch.Value = null;
// Assert
Assert.Null(@switch.Value);
Assert.True(@switch.IsValueSet);
}
[Fact]
public void ValueNonInterface_SettingValue_SetsIsValueSetToTrue()
{
// Arrange
var @switch = new NullableCompatibilitySwitch<bool>("TestProperty");
// Act
@switch.Value = false;
// Assert
Assert.False(@switch.Value);
Assert.True(@switch.IsValueSet);
}
[Fact]
public void ValueInterface_SettingValueToNull_SetsIsValueSetToTrue()
{
// Arrange
var @switch = new NullableCompatibilitySwitch<bool>("TestProperty");
// Act
((ICompatibilitySwitch)@switch).Value = null;
// Assert
Assert.Null(@switch.Value);
Assert.True(@switch.IsValueSet);
}
[Fact]
public void ValueInterface_SettingValue_SetsIsValueSetToTrue()
{
// Arrange
var @switch = new NullableCompatibilitySwitch<bool>("TestProperty");
// Act
((ICompatibilitySwitch)@switch).Value = true;
// Assert
Assert.True(@switch.Value);
Assert.True(@switch.IsValueSet);
}
}
}

View File

@ -62,14 +62,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var controller = new TestController(); var controller = new TestController();
var parameterBinder = new ParameterBinder(
var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetModelValidatorProvider(mockValidator.Object));
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -115,18 +112,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict); var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>())); mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
var validatorProvider = GetModelValidatorProvider(mockValidator.Object);
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var controller = new TestController(); var controller = new TestController();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetModelValidatorProvider(mockValidator.Object));
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -170,14 +165,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor); var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController(); var controller = new TestController();
@ -217,14 +207,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor); var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController(); var controller = new TestController();
@ -272,14 +257,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor); var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController(); var controller = new TestController();
@ -329,14 +309,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo)) .Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo))
.Returns(modelMetadata.Object); .Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
mockMetadataProvider.Object, mockMetadataProvider.Object,
factory, factory);
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -382,14 +357,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(p => p.GetMetadataForType(typeof(Person))) .Setup(p => p.GetMetadataForType(typeof(Person)))
.Returns(modelMetadata.Object); .Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
mockMetadataProvider.Object, mockMetadataProvider.Object,
factory, factory);
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -431,14 +401,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Returns(new[] { new ModelValidationResult("memberName", "some message") }); .Returns(new[] { new ModelValidationResult("memberName", "some message") });
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetModelValidatorProvider(mockValidator.Object));
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor,
NullLoggerFactory.Instance);
var controller = new TestController(); var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal); var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -489,9 +456,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameterBinder = new ParameterBinder( var parameterBinder = new ParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)),
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
Options.Create(mvcOptions), Options.Create(mvcOptions),
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
var controller = new TestController(); var controller = new TestController();
@ -538,14 +503,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder.Object); var factory = GetModelBinderFactory(binder.Object);
var controller = new TestController(); var controller = new TestController();
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetModelValidatorProvider(mockValidator.Object));
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -590,9 +551,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameterBinder = new ParameterBinder( var parameterBinder = new ParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)),
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -689,9 +648,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameterBinder = new ParameterBinder( var parameterBinder = new ParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory,
new DefaultObjectValidator( GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)),
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -731,14 +688,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory("Hello"); var factory = GetModelBinderFactory("Hello");
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -776,14 +728,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var expected = new List<string> { "Hello", "World", "!!" }; var expected = new List<string> { "Hello", "World", "!!" };
var factory = GetModelBinderFactory(expected); var factory = GetModelBinderFactory(expected);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -821,14 +768,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder); var factory = GetModelBinderFactory(binder);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value. // Some non default value.
controller.NonNullableProperty = -1; controller.NonNullableProperty = -1;
@ -867,14 +809,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder); var factory = GetModelBinderFactory(binder);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value. // Some non default value.
controller.NullableProperty = -1; controller.NullableProperty = -1;
@ -934,14 +871,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder); var factory = GetModelBinderFactory(binder);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value. // Some non default value.
controller.NullableProperty = -1; controller.NullableProperty = -1;
@ -1002,14 +934,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder); var factory = GetModelBinderFactory(binder);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Some non default value. // Some non default value.
controller.NullableProperty = -1; controller.NullableProperty = -1;
@ -1094,14 +1021,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(inputValue); var factory = GetModelBinderFactory(inputValue);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -1174,14 +1096,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory()); controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory());
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder( var parameterBinder = GetParameterBinder(
modelMetadataProvider, modelMetadataProvider,
factory, factory);
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
_optionsAccessor,
NullLoggerFactory.Instance);
// Act // Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -1278,7 +1195,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameterBinder = new Mock<ParameterBinder>( var parameterBinder = new Mock<ParameterBinder>(
new EmptyModelMetadataProvider(), new EmptyModelMetadataProvider(),
factory, factory,
new DefaultObjectValidator(modelMetadataProvider, new[] { modelValidatorProvider }), GetObjectValidator(modelMetadataProvider, modelValidatorProvider),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
parameterBinder.Setup(p => p.BindModelAsync( parameterBinder.Setup(p => p.BindModelAsync(
@ -1415,16 +1332,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
private static ParameterBinder GetParameterBinder( private static ParameterBinder GetParameterBinder(
IModelBinderFactory factory = null,
IObjectModelValidator validator = null,
IModelMetadataProvider modelMetadataProvider = null, IModelMetadataProvider modelMetadataProvider = null,
IModelBinderFactory factory = null,
IModelValidatorProvider modelValidatorProvider = null) IModelValidatorProvider modelValidatorProvider = null)
{ {
if (validator == null)
{
validator = CreateObjectValidator();
}
if (factory == null) if (factory == null)
{ {
factory = TestModelBinderFactory.CreateDefault(); factory = TestModelBinderFactory.CreateDefault();
@ -1436,9 +1347,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
var metadataProvider = modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider(); var metadataProvider = modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider();
var objectModelValidator = new DefaultObjectValidator( var objectModelValidator = GetObjectValidator(modelMetadataProvider, modelValidatorProvider);
metadataProvider,
new[] { modelValidatorProvider });
return new ParameterBinder( return new ParameterBinder(
metadataProvider, metadataProvider,
@ -1448,16 +1357,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
} }
private static IObjectModelValidator CreateObjectValidator() private static DefaultObjectValidator GetObjectValidator(
IModelMetadataProvider modelMetadataProvider,
IModelValidatorProvider validatorProvider)
{ {
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict); return new DefaultObjectValidator(
mockValidator modelMetadataProvider,
.Setup(o => o.Validate( new[] { validatorProvider },
It.IsAny<ActionContext>(), _options);
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
return mockValidator.Object;
} }
// No need for bind-related attributes on properties in this controller class. Properties are added directly // No need for bind-related attributes on properties in this controller class. Properties are added directly

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
@ -22,7 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public class DefaultObjectValidatorTests public class DefaultObjectValidatorTests
{ {
private IModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider(); private readonly MvcOptions _options = new MvcOptions();
private ModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider();
[Fact] [Fact]
public void Validate_SimpleValueType_Valid_WithPrefix() public void Validate_SimpleValueType_Valid_WithPrefix()
@ -1258,6 +1261,70 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}); });
} }
[Fact]
public void Validate_Throws_IfValidationDepthExceedsMaxDepth()
{
// Arrange
var maxDepth = 5;
var expected = $"ValidationVisitor exceeded the maximum configured validation depth '{maxDepth}' when validating property '{nameof(DepthObject.Depth)}' on type '{typeof(DepthObject)}'. " +
"This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type.";
_options.MaxValidationDepth = maxDepth;
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new DepthObject(maxDepth);
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void Validate_WorksIfObjectGraphIsSmallerThanMaxDepth()
{
// Arrange
var maxDepth = 5;
_options.MaxValidationDepth = maxDepth;
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new DepthObject(maxDepth - 1);
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
// Act & Assert
validator.Validate(actionContext, validationState, prefix: string.Empty, model);
Assert.True(actionContext.ModelState.IsValid);
}
[Fact]
public void Validate_Throws_WithMaxDepth_1()
{
// Arrange
var maxDepth = 1;
var expected = $"ValidationVisitor exceeded the maximum configured validation depth '{maxDepth}' when validating property '{nameof(DepthObject.Depth)}' on type '{typeof(DepthObject)}'. " +
"This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type.";
_options.MaxValidationDepth = maxDepth;
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new DepthObject(maxDepth + 1);
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLeveleMetadataData), BindingFlags.NonPublic | BindingFlags.Instance);
var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model));
Assert.Equal(expected, ex.Message);
Assert.NotNull(ex.HelpLink);
}
private static DefaultObjectValidator CreateValidator(Type excludedType) private static DefaultObjectValidator CreateValidator(Type excludedType)
{ {
var excludeFilters = new List<SuppressChildValidationMetadataProvider>(); var excludeFilters = new List<SuppressChildValidationMetadataProvider>();
@ -1268,14 +1335,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(excludeFilters.ToArray()); var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(excludeFilters.ToArray());
var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders; var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders;
return new DefaultObjectValidator(metadataProvider, validatorProviders); return new DefaultObjectValidator(metadataProvider, validatorProviders, new MvcOptions());
} }
private static DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers) private DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers)
{ {
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(providers); var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(providers);
var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders; var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders;
return new DefaultObjectValidator(metadataProvider, validatorProviders); return new DefaultObjectValidator(metadataProvider, validatorProviders, _options);
} }
private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys) private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys)
@ -1398,6 +1465,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
void DoSomething(); void DoSomething();
} }
private void Validate_Throws_ForTopLeveleMetadataData(DepthObject depthObject) { }
// Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member // Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member
// names are indexers. An example scenario is an attribute that confirms all entries in a list are unique. // names are indexers. An example scenario is an attribute that confirms all entries in a list are unique.
private class InvalidItemsAttribute : ValidationAttribute private class InvalidItemsAttribute : ValidationAttribute
@ -1442,5 +1511,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public string City { get; set; } public string City { get; set; }
} }
private class DepthObject
{
public DepthObject(int maxAllowedDepth, int depth = 0)
{
MaxAllowedDepth = maxAllowedDepth;
Depth = depth;
}
public int Depth { get; }
public int MaxAllowedDepth { get; }
public DepthObject Instance
{
get
{
if (Depth == MaxAllowedDepth - 1)
{
return this;
}
return new DepthObject(MaxAllowedDepth, Depth + 1);
}
}
}
} }
} }

View File

@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator })); new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert // Assert
Assert.False(result); Assert.False(result);
@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator })); new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert // Assert
Assert.True(result); Assert.True(result);
@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }), new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
propertyFilter); propertyFilter);
// Assert // Assert
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
TestModelMetadataProvider.CreateDefaultProvider(), TestModelMetadataProvider.CreateDefaultProvider(),
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }), new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
m => m.IncludedProperty, m => m.IncludedProperty,
m => m.MyProperty); m => m.MyProperty);
@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator })); new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert // Assert
// Includes everything. // Includes everything.
@ -531,7 +531,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }), new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
propertyFilter); propertyFilter);
// Assert // Assert
@ -599,7 +599,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
metadataProvider, metadataProvider,
GetModelBinderFactory(binderProviders), GetModelBinderFactory(binderProviders),
valueProvider, valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator })); new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert // Assert
Assert.True(result); Assert.True(result);

View File

@ -522,7 +522,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -576,7 +577,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -630,7 +632,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -683,7 +686,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -741,7 +745,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -802,7 +807,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -868,7 +874,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Mock.Of<IModelBinderFactory>(), Mock.Of<IModelBinderFactory>(),
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -947,7 +954,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
mockModelBinderFactory.Object, mockModelBinderFactory.Object,
new DefaultObjectValidator( new DefaultObjectValidator(
mockModelMetadataProvider.Object, mockModelMetadataProvider.Object,
new[] { GetModelValidatorProvider(validator) }), new[] { GetModelValidatorProvider(validator) },
new MvcOptions()),
optionsAccessor, optionsAccessor,
loggerFactory ?? NullLoggerFactory.Instance); loggerFactory ?? NullLoggerFactory.Instance);
} }

View File

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FormatterWebSite;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -209,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Arrange // Arrange
var invalidRequestData = "{\"FirstName\":\"Test\", \"LastName\": \"Testsson\"}"; var invalidRequestData = "{\"FirstName\":\"Test\", \"LastName\": \"Testsson\"}";
var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json");
var expectedErrorMessage = var expectedErrorMessage =
"{\"LastName\":[\"The field LastName must be a string with a maximum length of 5.\"]}"; "{\"LastName\":[\"The field LastName must be a string with a maximum length of 5.\"]}";
// Act // Act
@ -229,7 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Arrange // Arrange
var invalidRequestData = "{\"FirstName\":\"Testname\", \"LastName\": \"\"}"; var invalidRequestData = "{\"FirstName\":\"Testname\", \"LastName\": \"\"}";
var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json");
var expectedError = var expectedError =
"{\"LastName\":[\"The LastName field is required.\"]," + "{\"LastName\":[\"The LastName field is required.\"]," +
"\"FirstName\":[\"The field FirstName must be a string with a maximum length of 5.\"]}"; "\"FirstName\":[\"The field FirstName must be a string with a maximum length of 5.\"]}";
@ -243,5 +245,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var responseContent = await response.Content.ReadAsStringAsync(); var responseContent = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedError, actual: responseContent); Assert.Equal(expectedError, actual: responseContent);
} }
// Test for https://github.com/aspnet/Mvc/issues/7357
[Fact]
public async Task ValidationThrowsError_WhenValidationExceedsMaxValidationDepth()
{
// Arrange
var expected = $"ValidationVisitor exceeded the maximum configured validation depth '32' when validating property 'Value' on type '{typeof(RecursiveIdentifier)}'. " +
"This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type.";
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "Validation/ValidationThrowsError_WhenValidationExceedsMaxValidationDepth")
{
Content = new StringContent(@"{ ""Id"": ""S-1-5-21-1004336348-1177238915-682003330-512"" }", Encoding.UTF8, "application/json"),
};
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => Client.SendAsync(requestMessage));
Assert.Equal(expected, ex.Message);
}
} }
} }

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -79,7 +78,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
new ModelBinderFactory(metadataProvider, options, serviceProvider), new ModelBinderFactory(metadataProvider, options, serviceProvider),
new DefaultObjectValidator( new DefaultObjectValidator(
metadataProvider, metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }), new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) },
options.Value),
options, options,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
} }
@ -102,7 +102,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
new ModelBinderFactory(metadataProvider, options, services), new ModelBinderFactory(metadataProvider, options, services),
new DefaultObjectValidator( new DefaultObjectValidator(
metadataProvider, metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }), new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) },
options.Value),
options, options,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
} }
@ -127,7 +128,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{ {
return new DefaultObjectValidator( return new DefaultObjectValidator(
metadataProvider, metadataProvider,
GetModelValidatorProviders(options)); GetModelValidatorProviders(options),
options?.Value ?? new MvcOptions());
} }
private static IList<IModelValidatorProvider> GetModelValidatorProviders(IOptions<MvcOptions> options) private static IList<IModelValidatorProvider> GetModelValidatorProviders(IOptions<MvcOptions> options)

View File

@ -1555,7 +1555,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return new ParameterBinder( return new ParameterBinder(
metadataProvider, metadataProvider,
factory, factory,
new DefaultObjectValidator(metadataProvider, new[] { validator }), new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
Options.Create(mvcOptions), Options.Create(mvcOptions),
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
} }

View File

@ -553,7 +553,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelBinderFactory, modelBinderFactory,
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -678,7 +679,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelBinderFactory, modelBinderFactory,
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
_optionsAccessor, _optionsAccessor,
NullLoggerFactory.Instance); NullLoggerFactory.Instance);
@ -732,7 +734,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
modelBinderFactory, modelBinderFactory,
new DefaultObjectValidator( new DefaultObjectValidator(
modelMetadataProvider, modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }), new[] { TestModelValidatorProvider.CreateDefaultProvider() },
new MvcOptions()),
Options.Create(mvcOptions), Options.Create(mvcOptions),
NullLoggerFactory.Instance); NullLoggerFactory.Instance);

View File

@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.False(razorPagesOptions.AllowAreas); Assert.False(razorPagesOptions.AllowAreas);
Assert.False(mvcOptions.EnableEndpointRouting); Assert.False(mvcOptions.EnableEndpointRouting);
Assert.Null(mvcOptions.MaxValidationDepth);
} }
[Fact] [Fact]
@ -65,6 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.True(razorPagesOptions.AllowAreas); Assert.True(razorPagesOptions.AllowAreas);
Assert.False(mvcOptions.EnableEndpointRouting); Assert.False(mvcOptions.EnableEndpointRouting);
Assert.Null(mvcOptions.MaxValidationDepth);
} }
[Fact] [Fact]
@ -90,6 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.True(razorPagesOptions.AllowAreas); Assert.True(razorPagesOptions.AllowAreas);
Assert.True(mvcOptions.EnableEndpointRouting); Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
} }
[Fact] [Fact]
@ -115,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages);
Assert.True(razorPagesOptions.AllowAreas); Assert.True(razorPagesOptions.AllowAreas);
Assert.True(mvcOptions.EnableEndpointRouting); Assert.True(mvcOptions.EnableEndpointRouting);
Assert.Equal(32, mvcOptions.MaxValidationDepth);
} }
// This just does the minimum needed to be able to resolve these options. // This just does the minimum needed to be able to resolve these options.

View File

@ -458,7 +458,7 @@ namespace Microsoft.AspNetCore.Mvc.Test
{ {
ControllerContext = controllerContext, ControllerContext = controllerContext,
MetadataProvider = metadataProvider, MetadataProvider = metadataProvider,
ObjectValidator = new DefaultObjectValidator(metadataProvider, valiatorProviders), ObjectValidator = new DefaultObjectValidator(metadataProvider, valiatorProviders, new MvcOptions()),
TempData = tempData, TempData = tempData,
ViewData = viewData, ViewData = viewData,
}; };

View File

@ -0,0 +1,13 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties : Controller
{
[HttpPost]
public IActionResult EditPerson(DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel /*MM*/model) => null;
}
public class DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel
{
public string Model { get; }
}
}

View File

@ -0,0 +1,17 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class DiagnosticsAreReturned_ForModelBoundParameters : Controller
{
[HttpPost]
public IActionResult EditPerson(
[FromBody] DiagnosticsAreReturned_ForModelBoundParametersModel model,
[FromQuery] DiagnosticsAreReturned_ForModelBoundParametersModel /*MM*/value) => null;
}
public class DiagnosticsAreReturned_ForModelBoundParametersModel
{
public string Model { get; }
public string Value { get; }
}
}

View File

@ -0,0 +1,15 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName : Controller
{
[HttpPost]
public IActionResult Edit([ModelBinder(Name = "model")] DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel /*MM*/parameter) => null;
}
public class DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel
{
public string Model { get; }
public string Value { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class GetNameTests
{
public void NoAttribute(int param) { }
public void SingleAttribute([ModelBinder(Name = "testModelName")] int param) { }
public void SingleAttributeWithoutName([ModelBinder] int param) { }
public void MultipleAttributes([ModelBinder(Name = "name1")][Bind(Prefix = "name2")] int param) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_IgnoresFields
{
public string model;
public void ActionMethod(IsProblematicParameter_IgnoresFields model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_IgnoresMethods
{
public string Item() => null;
public void ActionMethod(IsProblematicParameter_IgnoresMethods item) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_IgnoresNonPublicProperties
{
protected string Model { get; set; }
public void ActionMethod(IsProblematicParameter_IgnoresNonPublicProperties model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_IgnoresStaticProperties
{
public static string Model { get; set; }
public void ActionMethod(IsProblematicParameter_IgnoresStaticProperties model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsFalse_ForFromBodyParameter
{
public string Model { get; set; }
public void ActionMethod([FromBody] IsProblematicParameter_ReturnsFalse_ForFromBodyParameter model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder
{
public string Model { get; set; }
public void ActionMethod([ModelBinder(typeof(object))] IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter
{
public string Model { get; set; }
public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter model) { }
}
}

View File

@ -0,0 +1,10 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty
{
[FromQuery(Name = "different")]
public string Model { get; set; }
public void ActionMethod(IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter
{
public string Model { get; set; }
public void ActionMethod([ModelBinder(Name = "model")] IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter different) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty
{
public string Model { get; set; }
public void ActionMethod(IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty model) { }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty
{
public string Model { get; set; }
public void ActionMethod([Bind(Prefix = "model")] IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty different) { }
}
}

View File

@ -0,0 +1,10 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter
{
[ModelBinder(typeof(object), Name = "model")]
public string Different { get; set; }
public void ActionMethod(IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter model) { }
}
}

View File

@ -0,0 +1,14 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
[ApiController]
public class NoDiagnosticsAreReturnedForApiControllers : Controller
{
[HttpPost]
public IActionResult EditPerson(NoDiagnosticsAreReturnedForApiControllersModel model) => null;
}
public class NoDiagnosticsAreReturnedForApiControllersModel
{
public string Model { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class NoDiagnosticsAreReturnedForNonActions : Controller
{
[NonAction]
public IActionResult EditPerson(NoDiagnosticsAreReturnedForNonActionsModel model) => null;
}
public class NoDiagnosticsAreReturnedForNonActionsModel
{
public string Model { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute : Controller
{
[HttpPost]
public IActionResult EditPerson([FromForm(Name = "")] NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttributeModel model) => null;
}
public class NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttributeModel
{
public string Model { get; }
}
}

View File

@ -0,0 +1,11 @@
namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles
{
public class SpecifiesModelTypeTests
{
public void SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType([ModelBinder(Name = "Name")] object model) { }
public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor([ModelBinder(typeof(object))] object model) { }
public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty([ModelBinder(BinderType = typeof(object))] object model) { }
}
}

View File

@ -0,0 +1,305 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles;
using Microsoft.CodeAnalysis;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Analyzers
{
public class TopLevelParameterNameAnalyzerTest
{
private MvcDiagnosticAnalyzerRunner Runner { get; } = new MvcDiagnosticAnalyzerRunner(new TopLevelParameterNameAnalyzer());
[Fact]
public Task DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties()
=> RunTest(nameof(DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel), "model");
[Fact]
public Task DiagnosticsAreReturned_ForModelBoundParameters()
=> RunTest(nameof(DiagnosticsAreReturned_ForModelBoundParametersModel), "value");
[Fact]
public Task DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName()
=> RunTest(nameof(DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel), "parameter");
[Fact]
public Task NoDiagnosticsAreReturnedForApiControllers()
=> RunNoDiagnosticsAreReturned();
[Fact]
public Task NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute()
=> RunNoDiagnosticsAreReturned();
[Fact]
public Task NoDiagnosticsAreReturnedForNonActions()
=> RunNoDiagnosticsAreReturned();
[Fact]
public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty()
{
var result = await IsProblematicParameterTest();
Assert.True(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty()
{
var result = await IsProblematicParameterTest();
Assert.True(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter()
{
var result = await IsProblematicParameterTest();
Assert.True(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter()
{
var result = await IsProblematicParameterTest();
Assert.True(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsFalse_ForFromBodyParameter()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_IgnoresStaticProperties()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_IgnoresFields()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_IgnoresMethods()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
[Fact]
public async Task IsProblematicParameter_IgnoresNonPublicProperties()
{
var result = await IsProblematicParameterTest();
Assert.False(result);
}
private async Task<bool> IsProblematicParameterTest([CallerMemberName] string testMethod = "")
{
var testSource = MvcTestSource.Read(GetType().Name, testMethod);
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
var compilation = await project.GetCompilationAsync();
var modelType = compilation.GetTypeByMetadataName($"Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles.{testMethod}");
var method = (IMethodSymbol)modelType.GetMembers("ActionMethod").First();
var parameter = method.Parameters[0];
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var result = TopLevelParameterNameAnalyzer.IsProblematicParameter(symbolCache, parameter);
return result;
}
[Fact]
public async Task GetName_ReturnsValueFromFirstAttributeWithValue()
{
var methodName = nameof(GetNameTests.SingleAttribute);
var compilation = await GetCompilationForGetName();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName);
var method = (IMethodSymbol)type.GetMembers(methodName).First();
var parameter = method.Parameters[0];
var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter);
Assert.Equal("testModelName", name);
}
[Fact]
public async Task GetName_ReturnsName_IfNoAttributesAreSpecified()
{
var methodName = nameof(GetNameTests.NoAttribute);
var compilation = await GetCompilationForGetName();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName);
var method = (IMethodSymbol)type.GetMembers(methodName).First();
var parameter = method.Parameters[0];
var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter);
Assert.Equal("param", name);
}
[Fact]
public async Task GetName_ReturnsName_IfAttributeDoesNotSpecifyName()
{
var methodName = nameof(GetNameTests.SingleAttributeWithoutName);
var compilation = await GetCompilationForGetName();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName);
var method = (IMethodSymbol)type.GetMembers(methodName).First();
var parameter = method.Parameters[0];
var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter);
Assert.Equal("param", name);
}
[Fact]
public async Task GetName_ReturnsFirstName_IfMultipleAttributesAreSpecified()
{
var methodName = nameof(GetNameTests.MultipleAttributes);
var compilation = await GetCompilationForGetName();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName);
var method = (IMethodSymbol)type.GetMembers(methodName).First();
var parameter = method.Parameters[0];
var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter);
Assert.Equal("name1", name);
}
private async Task<Compilation> GetCompilationForGetName()
{
var testSource = MvcTestSource.Read(GetType().Name, "GetNameTests");
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
var compilation = await project.GetCompilationAsync();
return compilation;
}
[Fact]
public async Task SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType()
{
var testMethod = nameof(SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType);
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
var compilation = await project.GetCompilationAsync();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
var parameter = method.Parameters[0];
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
Assert.False(result);
}
[Fact]
public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor()
{
var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor);
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
var compilation = await project.GetCompilationAsync();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
var parameter = method.Parameters[0];
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
Assert.True(result);
}
[Fact]
public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty()
{
var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty);
var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests");
var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });
var compilation = await project.GetCompilationAsync();
var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation);
var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName);
var method = (IMethodSymbol)type.GetMembers(testMethod).First();
var parameter = method.Parameters[0];
var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter);
Assert.True(result);
}
private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "")
{
// Arrange
var testSource = MvcTestSource.Read(GetType().Name, testMethod);
var expectedLocation = testSource.DefaultMarkerLocation;
// Act
var result = await Runner.GetDiagnosticsAsync(testSource.Source);
// Assert
Assert.Empty(result);
}
private async Task RunTest(string typeName, string parameterName, [CallerMemberName] string testMethod = "")
{
// Arrange
var descriptor = DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty;
var testSource = MvcTestSource.Read(GetType().Name, testMethod);
var expectedLocation = testSource.DefaultMarkerLocation;
// Act
var result = await Runner.GetDiagnosticsAsync(testSource.Source);
// Assert
Assert.Collection(
result,
diagnostic =>
{
Assert.Equal(descriptor.Id, diagnostic.Id);
Assert.Same(descriptor, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location);
Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), typeName, parameterName), diagnostic.GetMessage());
});
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.AspNetCore.Mvc.Analyzers;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Xunit; using Xunit;

View File

@ -78,5 +78,11 @@ namespace FormatterWebSite
return Ok(); return Ok();
} }
[HttpPost]
public IActionResult ValidationThrowsError_WhenValidationExceedsMaxValidationDepth([FromBody] InfinitelyRecursiveModel model)
{
return Ok();
}
} }
} }

View File

@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json;
namespace FormatterWebSite
{
public class InfinitelyRecursiveModel
{
[JsonConverter(typeof(StringIdentifierConverter))]
public RecursiveIdentifier Id { get; set; }
private class StringIdentifierConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(RecursiveIdentifier);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new RecursiveIdentifier(reader.Value.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace FormatterWebSite
{
// A System.Security.Principal.SecurityIdentifier like type that works on xplat
public class RecursiveIdentifier
{
public RecursiveIdentifier(string identifier)
{
Value = identifier;
}
public string Value { get; }
public RecursiveIdentifier AccountIdentifier => new RecursiveIdentifier(Value);
}
}

View File

@ -19,12 +19,12 @@ namespace FormatterWebSite
options.InputFormatters.Add(new StringInputFormatter()); options.InputFormatters.Add(new StringInputFormatter());
}) })
.AddXmlDataContractSerializerFormatters(); .AddXmlDataContractSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services.Configure<MvcJsonOptions>(options => { options.SerializerSettings.Converters.Insert(0, new IModelConverter()); }); services.Configure<MvcJsonOptions>(options => { options.SerializerSettings.Converters.Insert(0, new IModelConverter()); });
} }
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
app.UseMvc(routes => app.UseMvc(routes =>