Shortcircuit validation when using default validator providers and no validation metadata is discovered
Fixes https://github.com/aspnet/Mvc/issues/5887
This commit is contained in:
parent
a40c1f2d02
commit
fb57810f29
|
|
@ -132,6 +132,13 @@ namespace BasicApi.Controllers
|
||||||
return new CreatedAtRouteResult("FindPetById", new { id = pet.Id }, pet);
|
return new CreatedAtRouteResult("FindPetById", new { id = pet.Id }, pet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize("pet-store-writer")]
|
||||||
|
[HttpPost("add-pet")]
|
||||||
|
public ActionResult<Pet> AddPetWithoutDb(Pet pet)
|
||||||
|
{
|
||||||
|
return pet;
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize("pet-store-writer")]
|
[Authorize("pet-store-writer")]
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
public IActionResult EditPet(Pet pet)
|
public IActionResult EditPet(Pet pet)
|
||||||
|
|
|
||||||
|
|
@ -44,5 +44,11 @@
|
||||||
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua"
|
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua"
|
||||||
},
|
},
|
||||||
"Path": "/pet"
|
"Path": "/pet"
|
||||||
|
},
|
||||||
|
"BasicApi.PostWithoutDb": {
|
||||||
|
"Path": "/pet/add-pet",
|
||||||
|
"ClientProperties": {
|
||||||
|
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public abstract class ValidationVisitorBenchmarkBase
|
||||||
|
{
|
||||||
|
protected const int Iterations = 4;
|
||||||
|
|
||||||
|
protected static readonly IModelValidatorProvider[] ValidatorProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DefaultModelValidatorProvider(),
|
||||||
|
new DataAnnotationsModelValidatorProvider(
|
||||||
|
new ValidationAttributeAdapterProvider(),
|
||||||
|
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
|
||||||
|
null),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected static readonly CompositeModelValidatorProvider CompositeModelValidatorProvider = new CompositeModelValidatorProvider(ValidatorProviders);
|
||||||
|
|
||||||
|
public abstract object Model { get; }
|
||||||
|
|
||||||
|
public ModelMetadataProvider BaselineModelMetadataProvider { get; private set; }
|
||||||
|
public ModelMetadataProvider ModelMetadataProvider { get; private set; }
|
||||||
|
public ModelMetadata BaselineModelMetadata { get; private set; }
|
||||||
|
public ModelMetadata ModelMetadata { get; private set; }
|
||||||
|
public ActionContext ActionContext { get; private set; }
|
||||||
|
public ValidatorCache ValidatorCache { get; private set; }
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
BaselineModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: false);
|
||||||
|
ModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: true);
|
||||||
|
|
||||||
|
BaselineModelMetadata = BaselineModelMetadataProvider.GetMetadataForType(Model.GetType());
|
||||||
|
ModelMetadata = ModelMetadataProvider.GetMetadataForType(Model.GetType());
|
||||||
|
ActionContext = GetActionContext();
|
||||||
|
ValidatorCache = new ValidatorCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ModelMetadataProvider CreateModelMetadataProvider(bool addHasValidatorsProvider)
|
||||||
|
{
|
||||||
|
var detailsProviders = new List<IMetadataDetailsProvider>
|
||||||
|
{
|
||||||
|
new DefaultValidationMetadataProvider(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (addHasValidatorsProvider)
|
||||||
|
{
|
||||||
|
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(ValidatorProviders));
|
||||||
|
}
|
||||||
|
|
||||||
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ActionContext GetActionContext()
|
||||||
|
{
|
||||||
|
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public class ValidationVisitorByteArrayBenchmark : ValidationVisitorBenchmarkBase
|
||||||
|
{
|
||||||
|
public override object Model { get; } = new byte[30];
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, Description = "validation for byte arrays baseline", OperationsPerInvoke = Iterations)]
|
||||||
|
public void Baseline()
|
||||||
|
{
|
||||||
|
// Baseline for validating a byte array of size 30, without the ModelMetadata.HasValidators optimization.
|
||||||
|
// This is the behavior as of 2.1.
|
||||||
|
var validationVisitor = new ValidationVisitor(
|
||||||
|
ActionContext,
|
||||||
|
CompositeModelValidatorProvider,
|
||||||
|
ValidatorCache,
|
||||||
|
BaselineModelMetadataProvider,
|
||||||
|
new ValidationStateDictionary());
|
||||||
|
|
||||||
|
validationVisitor.Validate(BaselineModelMetadata, "key", Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "validation for byte arrays", OperationsPerInvoke = Iterations)]
|
||||||
|
public void HasValidators()
|
||||||
|
{
|
||||||
|
// Validating a byte array of size 30, with the ModelMetadata.HasValidators optimization.
|
||||||
|
var validationVisitor = new ValidationVisitor(
|
||||||
|
ActionContext,
|
||||||
|
CompositeModelValidatorProvider,
|
||||||
|
ValidatorCache,
|
||||||
|
ModelMetadataProvider,
|
||||||
|
new ValidationStateDictionary());
|
||||||
|
|
||||||
|
validationVisitor.Validate(ModelMetadata, "key", Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public class ValidationVisitorModelWithValidatedProperties : ValidationVisitorBenchmarkBase
|
||||||
|
{
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(20)]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public IList<Address> Address { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Street { get; set; }
|
||||||
|
|
||||||
|
public string Street2 { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Zip { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Model { get; } = new Person
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
Name = "Test",
|
||||||
|
Address = new List<Address>
|
||||||
|
{
|
||||||
|
new Address
|
||||||
|
{
|
||||||
|
Street = "1 Microsoft Way",
|
||||||
|
Type = "Work",
|
||||||
|
Zip = "98056",
|
||||||
|
},
|
||||||
|
new Address
|
||||||
|
{
|
||||||
|
Street = "15701 NE 39th St",
|
||||||
|
Type = "Home",
|
||||||
|
Zip = "98052",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true, Description = "validation for a model with some validated properties - baseline", OperationsPerInvoke = Iterations)]
|
||||||
|
public void Visit_TypeWithSomeValidatedProperties_Baseline()
|
||||||
|
{
|
||||||
|
// Baseline for validating a typical model with some properties that require validation.
|
||||||
|
// This executes without the ModelMetadata.HasValidators optimization.
|
||||||
|
|
||||||
|
var validationVisitor = new ValidationVisitor(
|
||||||
|
ActionContext,
|
||||||
|
CompositeModelValidatorProvider,
|
||||||
|
ValidatorCache,
|
||||||
|
BaselineModelMetadataProvider,
|
||||||
|
new ValidationStateDictionary());
|
||||||
|
|
||||||
|
validationVisitor.Validate(BaselineModelMetadata, "key", Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Description = "validation for a model with some validated properties", OperationsPerInvoke = Iterations)]
|
||||||
|
public void Visit_TypeWithSomeValidatedProperties()
|
||||||
|
{
|
||||||
|
// Validating a typical model with some properties that require validation.
|
||||||
|
// This executes with the ModelMetadata.HasValidators optimization.
|
||||||
|
var validationVisitor = new ValidationVisitor(
|
||||||
|
ActionContext,
|
||||||
|
CompositeModelValidatorProvider,
|
||||||
|
ValidatorCache,
|
||||||
|
ModelMetadataProvider,
|
||||||
|
new ValidationStateDictionary());
|
||||||
|
|
||||||
|
validationVisitor.Validate(ModelMetadata, "key", Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -325,6 +325,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract bool ValidateChildren { get; }
|
public abstract bool ValidateChildren { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that indicates if the model, or one of it's properties, or elements has associatated validators.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When <see langword="false"/>, validation can be assume that the model is valid (<see cref="ModelValidationState.Valid"/>) without
|
||||||
|
/// inspecting the object graph.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual bool? HasValidators { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of metadata items for validators.
|
/// Gets a collection of metadata items for validators.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
|
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,25 @@ using System.Threading;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets up default options for <see cref="MvcOptions"/>.
|
/// Sets up default options for <see cref="MvcOptions"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>
|
internal class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>, IPostConfigureOptions<MvcOptions>
|
||||||
{
|
{
|
||||||
private readonly IHttpRequestStreamReaderFactory _readerFactory;
|
private readonly IHttpRequestStreamReaderFactory _readerFactory;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
// Used in tests
|
|
||||||
public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
|
public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
|
||||||
: this(readerFactory, NullLoggerFactory.Instance)
|
: this(readerFactory, NullLoggerFactory.Instance)
|
||||||
{
|
{
|
||||||
|
|
@ -83,6 +84,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
|
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PostConfigure(string name, MvcOptions options)
|
||||||
|
{
|
||||||
|
// HasValidatorsValidationMetadataProvider uses the results of other ValidationMetadataProvider to determine if a model requires
|
||||||
|
// validation. It is imperative that this executes later than all other metadata provider. We'll register it as part of PostConfigure.
|
||||||
|
// This should ensure it appears later than all of the details provider registered by MVC and user configured details provider registered
|
||||||
|
// as part of ConfigureOptions.
|
||||||
|
options.ModelMetadataDetailsProviders.Add(new HasValidatorsValidationMetadataProvider(options.ModelValidatorProviders));
|
||||||
|
}
|
||||||
|
|
||||||
internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList<IMetadataDetailsProvider> modelMetadataDetailsProviders)
|
internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList<IMetadataDetailsProvider> modelMetadataDetailsProviders)
|
||||||
{
|
{
|
||||||
// Don't bind the Type class by default as it's expensive. A user can override this behavior
|
// Don't bind the Type class by default as it's expensive. A user can override this behavior
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
|
@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
private bool? _isRequired;
|
private bool? _isRequired;
|
||||||
private ModelPropertyCollection _properties;
|
private ModelPropertyCollection _properties;
|
||||||
private bool? _validateChildren;
|
private bool? _validateChildren;
|
||||||
|
private bool? _hasValidators;
|
||||||
private ReadOnlyCollection<object> _validatorMetadata;
|
private ReadOnlyCollection<object> _validatorMetadata;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -427,6 +429,84 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool? HasValidators
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_hasValidators.HasValue)
|
||||||
|
{
|
||||||
|
var visited = new HashSet<DefaultModelMetadata>();
|
||||||
|
|
||||||
|
_hasValidators = CalculateHasValidators(visited, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _hasValidators.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool CalculateHasValidators(HashSet<DefaultModelMetadata> visited, ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
|
if (metadata?.GetType() != typeof(DefaultModelMetadata))
|
||||||
|
{
|
||||||
|
// The calculation is valid only for DefaultModelMetadata instances. Null, other ModelMetadata instances
|
||||||
|
// or subtypes of DefaultModelMetadata will be treated as always requiring validation.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultModelMetadata = (DefaultModelMetadata)metadata;
|
||||||
|
|
||||||
|
if (defaultModelMetadata._hasValidators.HasValue)
|
||||||
|
{
|
||||||
|
// Return a previously calculated value if available.
|
||||||
|
return defaultModelMetadata._hasValidators.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultModelMetadata.ValidationMetadata.HasValidators != false)
|
||||||
|
{
|
||||||
|
// Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null).
|
||||||
|
// In either case, assume it has validators.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before inspecting properties or elements of a collection, ensure we do not have a cycle.
|
||||||
|
// Consider a model like so
|
||||||
|
//
|
||||||
|
// Employee { BusinessDivision Division; int Id; string Name; }
|
||||||
|
// BusinessDivision { int Id; List<Employee> Employees }
|
||||||
|
//
|
||||||
|
// If we get to the Employee element from Employee.Division.Employees, we can return false for that instance
|
||||||
|
// and allow other properties of BusinessDivision and Employee to determine if it has validators.
|
||||||
|
if (!visited.Add(defaultModelMetadata))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have inspected the current element. Inspect properties or elements that may contribute to this value.
|
||||||
|
if (defaultModelMetadata.IsEnumerableType)
|
||||||
|
{
|
||||||
|
if (CalculateHasValidators(visited, defaultModelMetadata.ElementMetadata))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (defaultModelMetadata.IsComplexType)
|
||||||
|
{
|
||||||
|
foreach (var property in defaultModelMetadata.Properties)
|
||||||
|
{
|
||||||
|
if (CalculateHasValidators(visited, property))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've come this far. The ModelMetadata does not have any validation
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IReadOnlyList<object> ValidatorMetadata
|
public override IReadOnlyList<object> ValidatorMetadata
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
|
{
|
||||||
|
internal class HasValidatorsValidationMetadataProvider : IValidationMetadataProvider
|
||||||
|
{
|
||||||
|
private readonly bool _hasOnlyMetadataBasedValidators;
|
||||||
|
private readonly IMetadataBasedModelValidatorProvider[] _validatorProviders;
|
||||||
|
|
||||||
|
public HasValidatorsValidationMetadataProvider(IList<IModelValidatorProvider> modelValidatorProviders)
|
||||||
|
{
|
||||||
|
if (modelValidatorProviders.Count > 0 && modelValidatorProviders.All(p => p is IMetadataBasedModelValidatorProvider))
|
||||||
|
{
|
||||||
|
_hasOnlyMetadataBasedValidators = true;
|
||||||
|
_validatorProviders = modelValidatorProviders.Cast<IMetadataBasedModelValidatorProvider>().ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_hasOnlyMetadataBasedValidators)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _validatorProviders.Length; i++)
|
||||||
|
{
|
||||||
|
var provider = _validatorProviders[i];
|
||||||
|
if (provider.HasValidators(context.Key.ModelType, context.ValidationMetadata.ValidatorMetadata))
|
||||||
|
{
|
||||||
|
context.ValidationMetadata.HasValidators = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.ValidationMetadata.HasValidators == null)
|
||||||
|
{
|
||||||
|
context.ValidationMetadata.HasValidators = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -41,5 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
/// in this list, to be consumed later by an <see cref="Validation.IModelValidatorProvider"/>.
|
/// in this list, to be consumed later by an <see cref="Validation.IModelValidatorProvider"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IList<object> ValidatorMetadata { get; } = new List<object>();
|
public IList<object> ValidatorMetadata { get; } = new List<object>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that indicates if the model has validators .
|
||||||
|
/// </summary>
|
||||||
|
public bool? HasValidators { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
// 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.ModelBinding.Validation;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A default <see cref="IModelValidatorProvider"/>.
|
/// A default <see cref="IModelValidatorProvider"/>.
|
||||||
|
|
@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
/// The <see cref="DefaultModelValidatorProvider"/> provides validators from <see cref="IModelValidator"/>
|
/// The <see cref="DefaultModelValidatorProvider"/> provides validators from <see cref="IModelValidator"/>
|
||||||
/// instances in <see cref="ModelBinding.ModelMetadata.ValidatorMetadata"/>.
|
/// instances in <see cref="ModelBinding.ModelMetadata.ValidatorMetadata"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class DefaultModelValidatorProvider : IModelValidatorProvider
|
internal sealed class DefaultModelValidatorProvider : IMetadataBasedModelValidatorProvider
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void CreateValidators(ModelValidatorProviderContext context)
|
public void CreateValidators(ModelValidatorProviderContext context)
|
||||||
|
|
@ -28,13 +29,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validator = validatorItem.ValidatorMetadata as IModelValidator;
|
if (validatorItem.ValidatorMetadata is IModelValidator validator)
|
||||||
if (validator != null)
|
|
||||||
{
|
{
|
||||||
validatorItem.Validator = validator;
|
validatorItem.Validator = validator;
|
||||||
validatorItem.IsReusable = true;
|
validatorItem.IsReusable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasValidators(Type modelType, IList<object> validatorMetadata)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < validatorMetadata.Count; i++)
|
||||||
|
{
|
||||||
|
if (validatorMetadata[i] is IModelValidator)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IModelValidatorProvider" /> that provides <see cref="IModelValidator"/> instances
|
||||||
|
/// exclusively using values in <see cref="ModelMetadata.ValidatorMetadata"/> or the model type.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="IMetadataBasedModelValidatorProvider" /> can be used to statically determine if a given
|
||||||
|
/// <see cref="ModelMetadata"/> instance can incur any validation. The value for <see cref="ModelMetadata.HasValidators"/>
|
||||||
|
/// can be calculated if all instances in <see cref="MvcOptions.ModelValidatorProviders"/> are <see cref="IMetadataBasedModelValidatorProvider" />.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public interface IMetadataBasedModelValidatorProvider : IModelValidatorProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that determines if the <see cref="IModelValidatorProvider"/> can
|
||||||
|
/// produce any validators given the <paramref name="modelType"/> and <paramref name="modelType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelType">The <see cref="Type"/> of the model.</param>
|
||||||
|
/// <param name="validatorMetadata">The list of metadata items for validators. <seealso cref="ValidationMetadata.ValidatorMetadata"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool HasValidators(Type modelType, IList<object> validatorMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -265,6 +265,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
CurrentPath.Pop(model);
|
CurrentPath.Pop(model);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// If the metadata indicates that no validators exist AND the aggregate state for the key says that the model graph
|
||||||
|
// is not invalid (i.e. is one of Unvalidated, Valid, or Skipped) we can safely mark the graph as valid.
|
||||||
|
else if (metadata.HasValidators == false &&
|
||||||
|
ModelState.GetFieldValidationState(key) != ModelValidationState.Invalid)
|
||||||
|
{
|
||||||
|
// No validators will be created for this graph of objects. Mark it as valid if it wasn't previously validated.
|
||||||
|
var entries = ModelState.FindKeysWithPrefix(key);
|
||||||
|
foreach (var item in entries)
|
||||||
|
{
|
||||||
|
if (item.Value.ValidationState == ModelValidationState.Unvalidated)
|
||||||
|
{
|
||||||
|
item.Value.ValidationState = ModelValidationState.Valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentPath.Pop(model);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
|
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,21 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An implementation of <see cref="IModelValidatorProvider"/> which provides validators
|
/// An implementation of <see cref="IModelValidatorProvider"/> which provides validators
|
||||||
/// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
|
/// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
|
||||||
/// a validator for types which implement <see cref="IValidatableObject"/>.
|
/// a validator for types which implement <see cref="IValidatableObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider
|
internal sealed class DataAnnotationsModelValidatorProvider : IMetadataBasedModelValidatorProvider
|
||||||
{
|
{
|
||||||
private readonly IOptions<MvcDataAnnotationsLocalizationOptions> _options;
|
private readonly IOptions<MvcDataAnnotationsLocalizationOptions> _options;
|
||||||
private readonly IStringLocalizerFactory _stringLocalizerFactory;
|
private readonly IStringLocalizerFactory _stringLocalizerFactory;
|
||||||
|
|
@ -66,8 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attribute = validatorItem.ValidatorMetadata as ValidationAttribute;
|
if (!(validatorItem.ValidatorMetadata is ValidationAttribute attribute))
|
||||||
if (attribute == null)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -98,5 +99,23 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasValidators(Type modelType, IList<object> validatorMetadata)
|
||||||
|
{
|
||||||
|
if (typeof(IValidatableObject).IsAssignableFrom(modelType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < validatorMetadata.Count; i++)
|
||||||
|
{
|
||||||
|
if (validatorMetadata[i] is ValidationAttribute)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,3 +4,9 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -248,6 +248,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
new Type[]
|
new Type[]
|
||||||
{
|
{
|
||||||
typeof(MvcOptionsConfigureCompatibilityOptions),
|
typeof(MvcOptionsConfigureCompatibilityOptions),
|
||||||
|
typeof(MvcCoreMvcOptionsSetup),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1170,11 +1170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var modelState = actionContext.ModelState;
|
var modelState = actionContext.ModelState;
|
||||||
var validationState = new ValidationStateDictionary();
|
var validationState = new ValidationStateDictionary();
|
||||||
|
|
||||||
var validator = CreateValidator(typeof(List<string>));
|
var validator = CreateValidator(typeof(List<ValidatedModel>));
|
||||||
|
|
||||||
var model = new List<string>()
|
var model = new List<ValidatedModel>()
|
||||||
{
|
{
|
||||||
"15",
|
new ValidatedModel { Value = "15" },
|
||||||
};
|
};
|
||||||
|
|
||||||
modelState.SetModelValue("userIds[0]", "15", "15");
|
modelState.SetModelValue("userIds[0]", "15", "15");
|
||||||
|
|
@ -1192,6 +1192,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Empty(entry.Errors);
|
Assert.Empty(entry.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ValidatedModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Validate_SuppressesValidation_ForExcludedType_Stream()
|
public void Validate_SuppressesValidation_ForExcludedType_Stream()
|
||||||
{
|
{
|
||||||
|
|
@ -1317,7 +1323,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{ model, new ValidationStateEntry() }
|
{ model, new ValidationStateEntry() }
|
||||||
};
|
};
|
||||||
var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance);
|
var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]);
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model));
|
var ex = Assert.Throws<InvalidOperationException>(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model));
|
||||||
|
|
@ -1325,6 +1330,102 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.NotNull(ex.HelpLink);
|
Assert.NotNull(ex.HelpLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_TypeWithoutValidators()
|
||||||
|
{
|
||||||
|
var actionContext = new ActionContext();
|
||||||
|
var validator = CreateValidator();
|
||||||
|
var model = new ModelWithoutValidation();
|
||||||
|
var validationState = new ValidationStateDictionary
|
||||||
|
{
|
||||||
|
{ model, new ValidationStateEntry() }
|
||||||
|
};
|
||||||
|
|
||||||
|
actionContext.ModelState.SetModelValue("Property1", new ValueProviderResult("value1"));
|
||||||
|
actionContext.ModelState.SetModelValue("Property2", new ValueProviderResult("value2"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(actionContext, validationState, string.Empty, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var modelState = actionContext.ModelState;
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
|
||||||
|
var entry = modelState["Property1"];
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = modelState["Property2"];
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_TypeWithoutValidators_DoesNotUpdateValidationState()
|
||||||
|
{
|
||||||
|
var actionContext = new ActionContext();
|
||||||
|
var validator = CreateValidator();
|
||||||
|
var model = new ModelWithoutValidation();
|
||||||
|
var validationState = new ValidationStateDictionary
|
||||||
|
{
|
||||||
|
{ model, new ValidationStateEntry() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var modelState = actionContext.ModelState;
|
||||||
|
modelState.SetModelValue("Property1", new ValueProviderResult("value1"));
|
||||||
|
modelState.SetModelValue("Property2", new ValueProviderResult("value2"));
|
||||||
|
modelState["Property2"].ValidationState = ModelValidationState.Skipped;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(actionContext, validationState, string.Empty, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
|
||||||
|
var entry = modelState["Property1"];
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = modelState["Property2"];
|
||||||
|
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_TypeWithoutValidators_DoesNotResetInvalidState()
|
||||||
|
{
|
||||||
|
var actionContext = new ActionContext();
|
||||||
|
var validator = CreateValidator();
|
||||||
|
var model = new ModelWithoutValidation();
|
||||||
|
var validationState = new ValidationStateDictionary
|
||||||
|
{
|
||||||
|
{ model, new ValidationStateEntry() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var modelState = actionContext.ModelState;
|
||||||
|
modelState.SetModelValue("Property1", new ValueProviderResult("value1"));
|
||||||
|
modelState.SetModelValue("Property2", new ValueProviderResult("value2"));
|
||||||
|
modelState["Property2"].ValidationState = ModelValidationState.Invalid;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
validator.Validate(actionContext, validationState, string.Empty, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
|
||||||
|
var entry = modelState["Property1"];
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = modelState["Property2"];
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelWithoutValidation
|
||||||
|
{
|
||||||
|
public string Property1 { get; set; }
|
||||||
|
|
||||||
|
public string Property2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private static DefaultObjectValidator CreateValidator(Type excludedType)
|
private static DefaultObjectValidator CreateValidator(Type excludedType)
|
||||||
{
|
{
|
||||||
var excludeFilters = new List<SuppressChildValidationMetadataProvider>();
|
var excludeFilters = new List<SuppressChildValidationMetadataProvider>();
|
||||||
|
|
@ -1352,6 +1453,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private class ThrowingProperty
|
private class ThrowingProperty
|
||||||
{
|
{
|
||||||
|
[Required]
|
||||||
public string WatchOut
|
public string WatchOut
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -1520,6 +1622,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Depth = depth;
|
Depth = depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Range(-10, 400)]
|
||||||
public int Depth { get; }
|
public int Depth { get; }
|
||||||
public int MaxAllowedDepth { get; }
|
public int MaxAllowedDepth { get; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
|
@ -909,6 +910,351 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
metadataProvider.VerifyAll();
|
metadataProvider.VerifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_ParameterMetadata_TypeHasNoValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = GetType()
|
||||||
|
.GetMethod(nameof(CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod), BindingFlags.Static | BindingFlags.NonPublic)
|
||||||
|
.GetParameters()[0];
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForParameter(parameter);
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod(string model) { }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_PropertyMetadata_TypeHasNoValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var property = GetType()
|
||||||
|
.GetProperty(nameof(CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty), BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, GetType());
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty { get; set; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_TypeWithoutProperties_TypeHasNoValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_SimpleType_TypeHasValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_ReturnsTrue_SimpleType_TypeHasNonDeterministicValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_TypeWithProperties_PropertyIsNotDefaultModelMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(TypeWithProperties);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
|
||||||
|
var propertyMetadata = new Mock<ModelMetadata>(propertyIdentity);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { propertyMetadata.Object, })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_TypeWithProperties_HasValidatorForAnyPropertyIsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(TypeWithProperties);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
|
||||||
|
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), typeof(string));
|
||||||
|
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, true);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { property1Metadata, property2Metadata })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_TypeWithProperties_HasValidatorsForPropertyIsNotDeterminstic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(TypeWithProperties);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
|
||||||
|
var propertyMetadata = CreateModelMetadata(propertyIdentity, metadataProvider.Object, null);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { propertyMetadata, })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_TypeWithProperties_HasValidatorForAllPropertiesIsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(TypeWithProperties);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), modelType);
|
||||||
|
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), modelType);
|
||||||
|
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { property1Metadata, property2Metadata })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_SelfReferencingType_HasValidatorOnNestedProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Employee);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
|
||||||
|
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
|
||||||
|
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
|
||||||
|
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
|
||||||
|
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
|
||||||
|
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
|
||||||
|
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
|
||||||
|
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, true); // BusinessUnit.Id has validators.
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
|
||||||
|
.Returns(new[] { unitHeadMetadata, unitIdMetadata, })
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_SelfReferencingType_HasValidatorOnSelfReferencedProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Employee);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
|
||||||
|
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
|
||||||
|
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
|
||||||
|
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
|
||||||
|
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
|
||||||
|
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, true); // BusinessUnit.Head has validators
|
||||||
|
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
|
||||||
|
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, });
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
|
||||||
|
.Returns(new[] { unitHeadMetadata, unitIdMetadata, });
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForType(modelType))
|
||||||
|
.Returns(modelMetadata);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_CollectionElementHasValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Employee);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
|
||||||
|
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
|
||||||
|
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { employeeIdMetadata, employeeEmployeesMetadata, });
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForType(modelType))
|
||||||
|
.Returns(CreateModelMetadata(modelIdentity, metadataProvider.Object, true)); // Employees.Employee has validators
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CalculateHasValidators_SelfReferencingType_NoValidatorsInGraph()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Employee);
|
||||||
|
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var metadataProvider = new Mock<IModelMetadataProvider>();
|
||||||
|
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
|
||||||
|
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
|
||||||
|
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
|
||||||
|
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
|
||||||
|
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
|
||||||
|
var employeeEmployeesId = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
|
||||||
|
var employeeEmployeesIdMetadata = CreateModelMetadata(employeeEmployeesId, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
|
||||||
|
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
|
||||||
|
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
|
||||||
|
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(modelType))
|
||||||
|
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesIdMetadata, });
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
|
||||||
|
.Returns(new[] { unitHeadMetadata, unitIdMetadata, });
|
||||||
|
|
||||||
|
metadataProvider
|
||||||
|
.Setup(mp => mp.GetMetadataForType(modelType))
|
||||||
|
.Returns(modelMetadata);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DefaultModelMetadata CreateModelMetadata(
|
||||||
|
ModelMetadataIdentity modelIdentity,
|
||||||
|
IModelMetadataProvider metadataProvider,
|
||||||
|
bool? hasValidators)
|
||||||
|
{
|
||||||
|
return new DefaultModelMetadata(
|
||||||
|
metadataProvider,
|
||||||
|
new SetHasValidatorsCompositeMetadataDetailsProvider { HasValidators = hasValidators },
|
||||||
|
new DefaultMetadataDetails(modelIdentity, new ModelAttributes(new object[0], new object[0], new object[0])));
|
||||||
|
}
|
||||||
|
|
||||||
private void ActionMethod(string input)
|
private void ActionMethod(string input)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -921,5 +1267,41 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
|
||||||
public int PublicGetPublicSetProperty { get; set; }
|
public int PublicGetPublicSetProperty { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Employee
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public BusinessUnit Unit { get; set; }
|
||||||
|
|
||||||
|
public Employee Manager { get; set; }
|
||||||
|
|
||||||
|
public List<Employee> Employees { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BusinessUnit
|
||||||
|
{
|
||||||
|
public Employee Head { get; set; }
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SetHasValidatorsCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider
|
||||||
|
{
|
||||||
|
public bool? HasValidators { get; set; }
|
||||||
|
|
||||||
|
public void CreateBindingMetadata(BindingMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
|
||||||
|
{
|
||||||
|
context.ValidationMetadata.HasValidators = HasValidators;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
||||||
|
{
|
||||||
|
public class HasValidatorsValidationMetadataProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateValidationMetadata_DoesNotSetHasValidators_IfNonMetadataBasedProviderExists()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validationProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DefaultModelValidatorProvider(),
|
||||||
|
Mock.Of<IModelValidatorProvider>(),
|
||||||
|
};
|
||||||
|
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
|
||||||
|
var context = new ValidationMetadataProviderContext(key, modelAttributes);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
metadataProvider.CreateValidationMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.ValidationMetadata.HasValidators);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateValidationMetadata_DoesNotSetHasValidators_IfProviderIsConfigured()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validationProviders = new IModelValidatorProvider[0];
|
||||||
|
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
|
||||||
|
var context = new ValidationMetadataProviderContext(key, modelAttributes);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
metadataProvider.CreateValidationMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.ValidationMetadata.HasValidators);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateValidationMetadata_SetsHasValidatorsToTrue_IfProviderReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var metadataBasedModelValidatorProvider = new Mock<IMetadataBasedModelValidatorProvider>();
|
||||||
|
metadataBasedModelValidatorProvider.Setup(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()))
|
||||||
|
.Returns(true)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var validationProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DefaultModelValidatorProvider(),
|
||||||
|
metadataBasedModelValidatorProvider.Object,
|
||||||
|
|
||||||
|
};
|
||||||
|
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
|
||||||
|
var context = new ValidationMetadataProviderContext(key, modelAttributes);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
metadataProvider.CreateValidationMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.ValidationMetadata.HasValidators);
|
||||||
|
metadataBasedModelValidatorProvider.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateValidationMetadata_SetsHasValidatorsToFalse_IfNoProviderReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = Mock.Of<IMetadataBasedModelValidatorProvider>(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()) == false);
|
||||||
|
var validationProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DefaultModelValidatorProvider(),
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
|
||||||
|
var context = new ValidationMetadataProviderContext(key, modelAttributes);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
metadataProvider.CreateValidationMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(context.ValidationMetadata.HasValidators);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateValidationMetadata_DoesNotOverrideExistingHasValidatorsValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = Mock.Of<IMetadataBasedModelValidatorProvider>(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()) == false);
|
||||||
|
var validationProviders = new IModelValidatorProvider[]
|
||||||
|
{
|
||||||
|
new DefaultModelValidatorProvider(),
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(typeof(object));
|
||||||
|
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
|
||||||
|
var context = new ValidationMetadataProviderContext(key, modelAttributes);
|
||||||
|
|
||||||
|
// Initialize this value.
|
||||||
|
context.ValidationMetadata.HasValidators = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
metadataProvider.CreateValidationMetadata(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.ValidationMetadata.HasValidators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,9 @@ using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
// Integration tests for the default configuration of ModelMetadata and Validation providers
|
// Integration tests for the default configuration of ModelMetadata and Validation providers
|
||||||
public class DefaultModelValidatorProviderTest
|
public class DefaultModelValidatorProviderTest
|
||||||
|
|
@ -145,6 +143,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is StringLengthAttribute);
|
Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is StringLengthAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidators_ReturnsTrue_IfMetadataIsIModelValidator()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validatorProvider = new DefaultModelValidatorProvider();
|
||||||
|
var attributes = new object[] { new RequiredAttribute(), new CustomModelValidatorAttribute(), new BindRequiredAttribute(), };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = validatorProvider.HasValidators(typeof(object), attributes);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidators_ReturnsFalse_IfNoMetadataIsIModelValidator()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var validatorProvider = new DefaultModelValidatorProvider();
|
||||||
|
var attributes = new object[] { new RequiredAttribute(), new BindRequiredAttribute(), };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = validatorProvider.HasValidators(typeof(object), attributes);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
private static IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
|
private static IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
return metadata.ValidatorMetadata.Select(v => new ValidatorItem(v)).ToList();
|
return metadata.ValidatorMetadata.Select(v => new ValidatorItem(v)).ToList();
|
||||||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -37,6 +38,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders);
|
MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders);
|
||||||
|
|
||||||
|
var validationProviders = TestModelValidatorProvider.CreateDefaultProvider();
|
||||||
|
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders));
|
||||||
|
|
||||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +61,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
||||||
detailsProviders.AddRange(providers);
|
detailsProviders.AddRange(providers);
|
||||||
|
|
||||||
|
var validationProviders = TestModelValidatorProvider.CreateDefaultProvider();
|
||||||
|
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders));
|
||||||
|
|
||||||
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
|
||||||
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
|
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
// 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.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.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;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
namespace Microsoft.AspNetCore.Mvc.DataAnnotations
|
||||||
{
|
{
|
||||||
public class DataAnnotationsModelValidatorProviderTest
|
public class DataAnnotationsModelValidatorProviderTest
|
||||||
{
|
{
|
||||||
|
|
@ -110,6 +112,56 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
||||||
Assert.Single(providerContext.Results);
|
Assert.Single(providerContext.Results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidators_ReturnsTrue_IfModelIsIValidatableObject()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider();
|
||||||
|
var mockValidatable = Mock.Of<IValidatableObject>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.HasValidators(mockValidatable.GetType(), Array.Empty<object>());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidators_ReturnsTrue_IfMetadataContainsValidationAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider();
|
||||||
|
var attributes = new object[] { new BindNeverAttribute(), new DummyValidationAttribute() };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.HasValidators(typeof(object), attributes);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidators_ReturnsFalse_IfNoDataAnnotationsValidationIsAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = GetProvider();
|
||||||
|
var attributes = new object[] { new BindNeverAttribute(), };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = provider.HasValidators(typeof(object), attributes);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataAnnotationsModelValidatorProvider GetProvider()
|
||||||
|
{
|
||||||
|
return new DataAnnotationsModelValidatorProvider(
|
||||||
|
new ValidationAttributeAdapterProvider(),
|
||||||
|
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
|
||||||
|
stringLocalizerFactory: null);
|
||||||
|
}
|
||||||
|
|
||||||
private IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
|
private IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
var items = new List<ValidatorItem>(metadata.ValidatorMetadata.Count);
|
var items = new List<ValidatorItem>(metadata.ValidatorMetadata.Count);
|
||||||
|
|
@ -262,5 +262,95 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => Client.SendAsync(requestMessage));
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => Client.SendAsync(requestMessage));
|
||||||
Assert.Equal(expected, ex.Message);
|
Assert.Equal(expected, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ErrorsDeserializingMalformedJson_AreReportedForModelsWithoutAnyValidationAttributes()
|
||||||
|
{
|
||||||
|
// This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation
|
||||||
|
// errors from json serialization errors
|
||||||
|
// Arrange
|
||||||
|
var input = "{Id = \"This string is incomplete";
|
||||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation")
|
||||||
|
{
|
||||||
|
Content = new StringContent(input, Encoding.UTF8, "application/json"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var validationProblemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(responseContent);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
validationProblemDetails.Errors,
|
||||||
|
error =>
|
||||||
|
{
|
||||||
|
Assert.Empty(error.Key);
|
||||||
|
Assert.Equal(new[] { "Invalid character after parsing property name. Expected ':' but got: =. Path '', line 1, position 4." }, error.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task JsonValidationErrors_AreReportedForModelsWithoutAnyValidationAttributes()
|
||||||
|
{
|
||||||
|
// This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation
|
||||||
|
// errors from json serialization errors
|
||||||
|
// Arrange
|
||||||
|
var input = "{Id: \"0c92bb85-cfaf-4344-8a9d-f92e88716861\"}";
|
||||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation")
|
||||||
|
{
|
||||||
|
Content = new StringContent(input, Encoding.UTF8, "application/json"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var validationProblemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(responseContent);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
validationProblemDetails.Errors,
|
||||||
|
error =>
|
||||||
|
{
|
||||||
|
Assert.Empty(error.Key);
|
||||||
|
Assert.Equal(new[] { "Required property 'isbn' not found in JSON. Path '', line 1, position 44." }, error.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ErrorsDeserializingMalformedXml_AreReportedForModelsWithoutAnyValidationAttributes()
|
||||||
|
{
|
||||||
|
// This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation
|
||||||
|
// errors from json serialization errors
|
||||||
|
// Arrange
|
||||||
|
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||||
|
"<BookModelWithNoValidation xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite.Models\">" +
|
||||||
|
"<Id>Incomplete element" +
|
||||||
|
"</BookModelWithNoValidation>";
|
||||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation")
|
||||||
|
{
|
||||||
|
Content = new StringContent(input, Encoding.UTF8, "application/xml"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await Client.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest);
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var validationProblemDetails = JsonConvert.DeserializeObject<ValidationProblemDetails>(responseContent);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
validationProblemDetails.Errors,
|
||||||
|
error =>
|
||||||
|
{
|
||||||
|
Assert.Empty(error.Key);
|
||||||
|
Assert.Equal(new[] { "An error occurred while deserializing input data." }, error.Value);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,13 +111,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
// Read-only collection should not be updated.
|
// Read-only collection should not be updated.
|
||||||
Assert.Empty(boundModel.Address);
|
Assert.Empty(boundModel.Address);
|
||||||
|
|
||||||
// ModelState (data is can't be validated).
|
Assert.True(modelState.IsValid);
|
||||||
Assert.False(modelState.IsValid);
|
|
||||||
var entry = Assert.Single(modelState);
|
var entry = Assert.Single(modelState);
|
||||||
Assert.Equal("Address[0].Street", entry.Key);
|
Assert.Equal("Address[0].Street", entry.Key);
|
||||||
var state = entry.Value;
|
var state = entry.Value;
|
||||||
Assert.NotNull(state);
|
Assert.NotNull(state);
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
|
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||||
Assert.Equal("SomeStreet", state.RawValue);
|
Assert.Equal("SomeStreet", state.RawValue);
|
||||||
Assert.Equal("SomeStreet", state.AttemptedValue);
|
Assert.Equal("SomeStreet", state.AttemptedValue);
|
||||||
}
|
}
|
||||||
|
|
@ -292,12 +291,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
Assert.Empty(boundModel.Address);
|
Assert.Empty(boundModel.Address);
|
||||||
|
|
||||||
// ModelState (data cannot be validated).
|
// ModelState (data cannot be validated).
|
||||||
Assert.False(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
var entry = Assert.Single(modelState);
|
var entry = Assert.Single(modelState);
|
||||||
Assert.Equal("prefix.Address[0].Street", entry.Key);
|
Assert.Equal("prefix.Address[0].Street", entry.Key);
|
||||||
var state = entry.Value;
|
var state = entry.Value;
|
||||||
Assert.NotNull(state);
|
Assert.NotNull(state);
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
|
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||||
Assert.Equal("SomeStreet", state.AttemptedValue);
|
Assert.Equal("SomeStreet", state.AttemptedValue);
|
||||||
Assert.Equal("SomeStreet", state.RawValue);
|
Assert.Equal("SomeStreet", state.RawValue);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
|
{
|
||||||
|
public class HasValidatorsValidationMetadataProviderIntegrationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void HasValidatorsValidationMetadataProvider_IsRegisteredAfterOtherMetadataProviders()
|
||||||
|
{
|
||||||
|
// HasValidatorsValidationMetadataProvider uses values populated by other details providers to query validator providers
|
||||||
|
// This test ensures all other detail providers have had an opportunity to modify validation metadata first.
|
||||||
|
// Arrange
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddLogging();
|
||||||
|
serviceCollection.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||||
|
serviceCollection.AddMvc();
|
||||||
|
var services = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var options = services.GetRequiredService<IOptions<MvcOptions>>();
|
||||||
|
|
||||||
|
Assert.IsType<HasValidatorsValidationMetadataProvider>(options.Value.ModelMetadataDetailsProviders.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasValidatorsValidationMetadataProvider_IsRegisteredAfterUserSpecifiedMetadataProvider()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddLogging();
|
||||||
|
serviceCollection.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||||
|
serviceCollection.AddMvc(mvcOptions =>
|
||||||
|
{
|
||||||
|
mvcOptions.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IQueryable)));
|
||||||
|
});
|
||||||
|
var services = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var options = services.GetRequiredService<IOptions<MvcOptions>>();
|
||||||
|
|
||||||
|
Assert.IsType<HasValidatorsValidationMetadataProvider>(options.Value.ModelMetadataDetailsProviders.Last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -373,15 +373,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
|
var result = await TryUpdateModelAsync(model, string.Empty, testContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.True(result);
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.False(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
var entry = Assert.Single(modelState);
|
var entry = Assert.Single(modelState);
|
||||||
Assert.Equal("Address[0].Street", entry.Key);
|
Assert.Equal("Address[0].Street", entry.Key);
|
||||||
var state = entry.Value;
|
var state = entry.Value;
|
||||||
Assert.NotNull(state);
|
Assert.NotNull(state);
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
|
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||||
Assert.Equal("SomeStreet", state.RawValue);
|
Assert.Equal("SomeStreet", state.RawValue);
|
||||||
Assert.Equal("SomeStreet", state.AttemptedValue);
|
Assert.Equal("SomeStreet", state.AttemptedValue);
|
||||||
}
|
}
|
||||||
|
|
@ -402,15 +402,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
var result = await TryUpdateModelAsync(model, "prefix", testContext);
|
var result = await TryUpdateModelAsync(model, "prefix", testContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.True(result);
|
||||||
|
|
||||||
// ModelState
|
// ModelState
|
||||||
Assert.False(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
var entry = Assert.Single(modelState);
|
var entry = Assert.Single(modelState);
|
||||||
Assert.Equal("prefix.Address[0].Street", entry.Key);
|
Assert.Equal("prefix.Address[0].Street", entry.Key);
|
||||||
var state = entry.Value;
|
var state = entry.Value;
|
||||||
Assert.NotNull(state);
|
Assert.NotNull(state);
|
||||||
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
|
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
|
||||||
Assert.Equal("SomeStreet", state.RawValue);
|
Assert.Equal("SomeStreet", state.RawValue);
|
||||||
Assert.Equal("SomeStreet", state.AttemptedValue);
|
Assert.Equal("SomeStreet", state.AttemptedValue);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
Assert.Equal(2, modelStateErrors.Count);
|
Assert.Equal(2, modelStateErrors.Count);
|
||||||
AssertErrorEquals("Property", modelStateErrors["Message"]);
|
AssertErrorEquals("Property", modelStateErrors["Message"]);
|
||||||
AssertErrorEquals("Model", modelStateErrors[""]);
|
AssertErrorEquals("Model", modelStateErrors[""]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -183,7 +182,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
var modelStateErrors = GetModelStateErrors(modelState);
|
var modelStateErrors = GetModelStateErrors(modelState);
|
||||||
Assert.Single(modelStateErrors); // single error from the required attribute
|
Assert.Single(modelStateErrors); // single error from the required attribute
|
||||||
AssertErrorEquals("Property", modelStateErrors.Single().Value);
|
AssertErrorEquals("Property", modelStateErrors.Single().Value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ModelLevelError]
|
[ModelLevelError]
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -14,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -1488,6 +1493,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
public string Control { get; set; }
|
public string Control { get; set; }
|
||||||
|
|
||||||
[ValidateSometimes(nameof(Control))]
|
[ValidateSometimes(nameof(Control))]
|
||||||
|
[Range(0, 10)]
|
||||||
public int ControlLength => Control.Length;
|
public int ControlLength => Control.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1571,6 +1577,53 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This type has a IPropertyValidationFilter declared on a property, but no validators.
|
||||||
|
// We should expect validation to short-circuit
|
||||||
|
private class ValidateSomePropertiesSometimesWithoutValidation
|
||||||
|
{
|
||||||
|
public string Control { get; set; }
|
||||||
|
|
||||||
|
[ValidateSometimes(nameof(Control))]
|
||||||
|
public int ControlLength => Control.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PropertyToSometimesSkip_IsNotValidated_IfNoValidationAttributesExistButPropertyValidationFilterExists()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameter = new ParameterDescriptor
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(ValidateSomePropertiesSometimesWithoutValidation),
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext();
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Add an entry for the ControlLength property so that we can observe Skipped versus Valid states.
|
||||||
|
modelState.SetModelValue(
|
||||||
|
nameof(ValidateSomePropertiesSometimes.ControlLength),
|
||||||
|
rawValue: null,
|
||||||
|
attemptedValue: null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsModelSet);
|
||||||
|
var model = Assert.IsType<ValidateSomePropertiesSometimesWithoutValidation>(result.Model);
|
||||||
|
Assert.Null(model.Control);
|
||||||
|
|
||||||
|
// Note this Exception is not thrown earlier.
|
||||||
|
Assert.Throws<NullReferenceException>(() => model.ControlLength);
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
var kvp = Assert.Single(modelState);
|
||||||
|
Assert.Equal(nameof(ValidateSomePropertiesSometimesWithoutValidation.ControlLength), kvp.Key);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
private class Order11
|
private class Order11
|
||||||
{
|
{
|
||||||
public IEnumerable<Address> ShippingAddresses { get; set; }
|
public IEnumerable<Address> ShippingAddresses { get; set; }
|
||||||
|
|
@ -1838,6 +1891,550 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_NoAttributeInGraphOfObjects_WithDefaultValidatorProviders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Order12),
|
||||||
|
BindingInfo = new BindingInfo
|
||||||
|
{
|
||||||
|
BindingSource = BindingSource.Body
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var input = new Order12
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
OrderFile = new byte[40],
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(input)));
|
||||||
|
request.ContentType = "application/json";
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Order12>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(input.Id, model.Id);
|
||||||
|
Assert.Equal(input.OrderFile, model.OrderFile);
|
||||||
|
Assert.Null(model.RelatedOrders);
|
||||||
|
|
||||||
|
Assert.Empty(modelState);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Order12
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public byte[] OrderFile { get; set; }
|
||||||
|
|
||||||
|
public IList<Order12> RelatedOrders { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_ListOfType_NoValidatorOnParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterInfo = GetType().GetMethod(nameof(Validation_ListOfType_NoValidatorOnParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
|
.GetParameters()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = parameterInfo.Name,
|
||||||
|
ParameterType = parameterInfo.ParameterType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?[0]=1&[1]=2");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<List<int>>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(new[] { 1, 2 }, model);
|
||||||
|
|
||||||
|
Assert.False(modelMetadata.HasValidators);
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "[0]").Value;
|
||||||
|
Assert.Equal("1", entry.AttemptedValue);
|
||||||
|
Assert.Equal("1", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "[1]").Value;
|
||||||
|
Assert.Equal("2", entry.AttemptedValue);
|
||||||
|
Assert.Equal("2", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Validation_ListOfType_NoValidatorOnParameterTestMethod(List<int> parameter) { }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_ListOfType_ValidatorOnParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterInfo = GetType().GetMethod(nameof(Validation_ListOfType_ValidatorOnParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
|
.GetParameters()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = parameterInfo.Name,
|
||||||
|
ParameterType = parameterInfo.ParameterType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?[0]=1&[1]=2");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<List<int>>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(new[] { 1, 2 }, model);
|
||||||
|
|
||||||
|
Assert.True(modelMetadata.HasValidators);
|
||||||
|
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "").Value;
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "[0]").Value;
|
||||||
|
Assert.Equal("1", entry.AttemptedValue);
|
||||||
|
Assert.Equal("1", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "[1]").Value;
|
||||||
|
Assert.Equal("2", entry.AttemptedValue);
|
||||||
|
Assert.Equal("2", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Validation_ListOfType_ValidatorOnParameterTestMethod([ConsistentMinLength(3)] List<int> parameter) { }
|
||||||
|
|
||||||
|
private class ConsistentMinLength : ValidationAttribute
|
||||||
|
{
|
||||||
|
private readonly int _length;
|
||||||
|
|
||||||
|
public ConsistentMinLength(int length)
|
||||||
|
{
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsValid(object value)
|
||||||
|
{
|
||||||
|
return value is ICollection collection && collection.Count >= _length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_CollectionOfType_ValidatorOnElement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterInfo = GetType().GetMethod(nameof(Validation_CollectionOfType_ValidatorOnElementTestMethod), BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
|
.GetParameters()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = parameterInfo.Name,
|
||||||
|
ParameterType = parameterInfo.ParameterType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?p[0].Id=1&p[1].Id=2");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Collection<InvalidEvenIds>>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(1, model[0].Id);
|
||||||
|
Assert.Equal(2, model[1].Id);
|
||||||
|
|
||||||
|
Assert.True(modelMetadata.HasValidators);
|
||||||
|
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "p[0].Id").Value;
|
||||||
|
Assert.Equal("1", entry.AttemptedValue);
|
||||||
|
Assert.Equal("1", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "p[1]").Value;
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "p[1].Id").Value;
|
||||||
|
Assert.Equal("2", entry.AttemptedValue);
|
||||||
|
Assert.Equal("2", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Validation_CollectionOfType_ValidatorOnElementTestMethod(Collection<InvalidEvenIds> p) { }
|
||||||
|
|
||||||
|
public class InvalidEvenIds : IValidatableObject
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
if (Id % 2 == 0)
|
||||||
|
{
|
||||||
|
yield return new ValidationResult("Failed validation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_DictionaryType_NoValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(IDictionary<string, int>)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value=10");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model.OrderBy(k => k.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key0", kvp.Key);
|
||||||
|
Assert.Equal(10, kvp.Value);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value;
|
||||||
|
Assert.Equal("key0", entry.AttemptedValue);
|
||||||
|
Assert.Equal("key0", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value").Value;
|
||||||
|
Assert.Equal("10", entry.AttemptedValue);
|
||||||
|
Assert.Equal("10", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_DictionaryType_ValueHasValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(Dictionary<string, NeverValid>)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value.NeverValidProperty=value0");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Dictionary<string, NeverValid>>(modelBindingResult.Model);
|
||||||
|
Assert.Collection(
|
||||||
|
model.OrderBy(k => k.Key),
|
||||||
|
kvp =>
|
||||||
|
{
|
||||||
|
Assert.Equal("key0", kvp.Key);
|
||||||
|
Assert.Equal("value0", kvp.Value.NeverValidProperty);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value;
|
||||||
|
Assert.Equal("key0", entry.AttemptedValue);
|
||||||
|
Assert.Equal("key0", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value.NeverValidProperty").Value;
|
||||||
|
Assert.Equal("value0", entry.AttemptedValue);
|
||||||
|
Assert.Equal("value0", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value").Value;
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||||
|
Assert.Single(entry.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_TopLevelProperty_NoValidation()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Validation_TopLevelPropertyController);
|
||||||
|
var propertyInfo = modelType.GetProperty(nameof(Validation_TopLevelPropertyController.Model));
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, propertyInfo.PropertyType);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = propertyInfo.Name,
|
||||||
|
ParameterType = propertyInfo.PropertyType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?Model.Id=12");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Validation_TopLevelPropertyModel>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(12, model.Id);
|
||||||
|
|
||||||
|
Assert.False(modelMetadata.HasValidators);
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "Model.Id").Value;
|
||||||
|
Assert.Equal("12", entry.AttemptedValue);
|
||||||
|
Assert.Equal("12", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Validation_TopLevelPropertyModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Validation_TopLevelPropertyController
|
||||||
|
{
|
||||||
|
public Validation_TopLevelPropertyModel Model { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_TopLevelProperty_ValidationOnProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(Validation_TopLevelProperty_ValidationOnPropertyController);
|
||||||
|
var propertyInfo = modelType.GetProperty(nameof(Validation_TopLevelProperty_ValidationOnPropertyController.Model));
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, propertyInfo.PropertyType);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = propertyInfo.Name,
|
||||||
|
ParameterType = propertyInfo.PropertyType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?Model.Id=12");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<Validation_TopLevelPropertyModel>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(12, model.Id);
|
||||||
|
|
||||||
|
Assert.True(modelMetadata.HasValidators);
|
||||||
|
|
||||||
|
Assert.False(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "Model.Id").Value;
|
||||||
|
Assert.Equal("12", entry.AttemptedValue);
|
||||||
|
Assert.Equal("12", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
|
||||||
|
entry = Assert.Single(modelState, e => e.Key == "Model").Value;
|
||||||
|
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Validation_TopLevelProperty_ValidationOnPropertyController
|
||||||
|
{
|
||||||
|
[CustomValidation(typeof(Validation_TopLevelProperty_ValidationOnPropertyController), nameof(Validate))]
|
||||||
|
public Validation_TopLevelPropertyModel Model { get; set; }
|
||||||
|
|
||||||
|
public static ValidationResult Validate(ValidationContext context)
|
||||||
|
{
|
||||||
|
return new ValidationResult("Invalid result");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_InfinitelyRecursiveType_NoValidators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = "parameter",
|
||||||
|
ParameterType = typeof(RecursiveModel)
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?Property1=8");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<RecursiveModel>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(8, model.Property1);
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "Property1").Value;
|
||||||
|
Assert.Equal("8", entry.AttemptedValue);
|
||||||
|
Assert.Equal("8", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecursiveModel
|
||||||
|
{
|
||||||
|
public int Property1 { get; set; }
|
||||||
|
|
||||||
|
public RecursiveModel Property2 { get; set; }
|
||||||
|
|
||||||
|
public RecursiveModel Property3 => new RecursiveModel { Property1 = Property1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var parameterInfo = GetType().GetMethod(nameof(Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod), BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
|
.GetParameters()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||||
|
var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo);
|
||||||
|
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
|
||||||
|
|
||||||
|
var parameter = new ParameterDescriptor()
|
||||||
|
{
|
||||||
|
Name = parameterInfo.Name,
|
||||||
|
ParameterType = parameterInfo.ParameterType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var testContext = ModelBindingTestHelper.GetTestContext(request =>
|
||||||
|
{
|
||||||
|
request.QueryString = new QueryString("?Property1=8");
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
|
var model = Assert.IsType<RecursiveModel>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(8, model.Property1);
|
||||||
|
|
||||||
|
Assert.True(modelState.IsValid);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
|
||||||
|
|
||||||
|
var entry = Assert.Single(modelState, e => e.Key == "Property1").Value;
|
||||||
|
Assert.Equal("8", entry.AttemptedValue);
|
||||||
|
Assert.Equal("8", entry.RawValue);
|
||||||
|
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod([Required] RecursiveModel model) { }
|
||||||
|
|
||||||
private static void AssertRequiredError(string key, ModelError error)
|
private static void AssertRequiredError(string key, ModelError error)
|
||||||
{
|
{
|
||||||
Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage(key), error.ErrorMessage);
|
Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage(key), error.ErrorMessage);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.JsonPatch;
|
using Microsoft.AspNetCore.JsonPatch;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
using Microsoft.AspNetCore.Mvc.DataAnnotations;
|
||||||
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
@ -251,7 +252,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
|
var excludeFilter = Assert.IsType<SuppressChildValidationMetadataProvider>(provider);
|
||||||
Assert.Equal(typeof(XmlNode).FullName, excludeFilter.FullTypeName);
|
Assert.Equal(typeof(XmlNode).FullName, excludeFilter.FullTypeName);
|
||||||
});
|
},
|
||||||
|
provider => Assert.IsType<HasValidatorsValidationMetadataProvider>(provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T GetOptions<T>(Action<IServiceCollection> action = null)
|
private static T GetOptions<T>(Action<IServiceCollection> action = null)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
using Microsoft.AspNetCore.Mvc.Formatters.Json;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
|
using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
|
@ -381,14 +382,15 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
typeof(IPostConfigureOptions<MvcOptions>),
|
typeof(IPostConfigureOptions<MvcOptions>),
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
typeof(MvcOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.Infrastructure.MvcOptionsConfigureCompatibilityOptions", throwOnError: true),
|
typeof(MvcOptionsConfigureCompatibilityOptions),
|
||||||
|
typeof(MvcCoreMvcOptionsSetup),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
typeof(IPostConfigureOptions<RazorPagesOptions>),
|
typeof(IPostConfigureOptions<RazorPagesOptions>),
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
typeof(RazorPagesOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptionsConfigureCompatibilityOptions", throwOnError: true),
|
typeof(RazorPagesOptionsConfigureCompatibilityOptions),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// 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 FormatterWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]/[action]")]
|
||||||
|
public class TestApiController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult PostBookWithNoValidation(BookModelWithNoValidation bookModel) => Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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.Runtime.Serialization;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace FormatterWebSite.Models
|
||||||
|
{
|
||||||
|
public class BookModelWithNoValidation
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonRequired]
|
||||||
|
[DataMember(IsRequired = true)]
|
||||||
|
public string ISBN { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
// 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.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace FormatterWebSite
|
namespace FormatterWebSite
|
||||||
{
|
{
|
||||||
// A System.Security.Principal.SecurityIdentifier like type that works on xplat
|
// A System.Security.Principal.SecurityIdentifier like type that works on xplat
|
||||||
public class RecursiveIdentifier
|
public class RecursiveIdentifier : IValidatableObject
|
||||||
{
|
{
|
||||||
public RecursiveIdentifier(string identifier)
|
public RecursiveIdentifier(string identifier)
|
||||||
{
|
{
|
||||||
|
|
@ -14,5 +18,10 @@ namespace FormatterWebSite
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
public RecursiveIdentifier AccountIdentifier => new RecursiveIdentifier(Value);
|
public RecursiveIdentifier AccountIdentifier => new RecursiveIdentifier(Value);
|
||||||
|
|
||||||
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<ValidationResult>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue