File: ModelBinding\Validation\DefaultComplexObjectValidationStrategy.cs
Web Access
Project: src\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Collections;
using Microsoft.AspNetCore.Mvc.Core;
 
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
 
/// <summary>
/// The default implementation of <see cref="IValidationStrategy"/> for a complex object.
/// </summary>
internal sealed class DefaultComplexObjectValidationStrategy : IValidationStrategy
{
    /// <summary>
    /// Gets an instance of <see cref="DefaultComplexObjectValidationStrategy"/>.
    /// </summary>
    public static readonly IValidationStrategy Instance = new DefaultComplexObjectValidationStrategy();
 
    private DefaultComplexObjectValidationStrategy()
    {
    }
 
    /// <inheritdoc />
    public IEnumerator<ValidationEntry> GetChildren(
        ModelMetadata metadata,
        string key,
        object model)
    {
        return new Enumerator(metadata, key, model);
    }
 
    private sealed class Enumerator : IEnumerator<ValidationEntry>
    {
        private readonly string _key;
        private readonly object _model;
        private readonly int _count;
        private readonly ModelMetadata _modelMetadata;
        private readonly IReadOnlyList<ModelMetadata> _parameters;
        private readonly IReadOnlyList<ModelMetadata> _properties;
 
        private ValidationEntry _entry;
        private int _index;
 
        public Enumerator(
            ModelMetadata modelMetadata,
            string key,
            object model)
        {
            _modelMetadata = modelMetadata;
            _key = key;
            _model = model;
 
            if (_modelMetadata.BoundConstructor == null)
            {
                _parameters = Array.Empty<ModelMetadata>();
            }
            else
            {
                _modelMetadata.ThrowIfRecordTypeHasValidationOnProperties();
                _parameters = _modelMetadata.BoundConstructor.BoundConstructorParameters!;
            }
 
            _properties = _modelMetadata.BoundProperties;
            _count = _properties.Count + _parameters.Count;
 
            _index = -1;
        }
 
        public ValidationEntry Current => _entry;
 
        object IEnumerator.Current => Current;
 
        public bool MoveNext()
        {
            _index++;
 
            if (_index >= _count)
            {
                return false;
            }
 
            if (_index < _parameters.Count)
            {
                var parameter = _parameters[_index];
                var parameterName = parameter.BinderModelName ?? parameter.ParameterName;
                var key = ModelNames.CreatePropertyModelName(_key, parameterName);
 
                if (_model is null)
                {
                    _entry = new ValidationEntry(parameter, key, model: null);
                }
                else
                {
                    if (!_modelMetadata.BoundConstructorParameterMapping.TryGetValue(parameter, out var property))
                    {
                        throw new InvalidOperationException(
                            Resources.FormatValidationStrategy_MappedPropertyNotFound(parameterName, _modelMetadata.ModelType));
                    }
 
                    _entry = new ValidationEntry(parameter, key, () => GetModel(_model, property));
                }
            }
            else
            {
                var property = _properties[_index - _parameters.Count];
                var propertyName = property.ValidationModelName ?? property.BinderModelName ?? property.PropertyName;
                var key = ModelNames.CreatePropertyModelName(_key, propertyName);
 
                if (_model == null)
                {
                    // Performance: Never create a delegate when container is null.
                    _entry = new ValidationEntry(property, key, model: null);
                }
                else
                {
                    _entry = new ValidationEntry(property, key, () => GetModel(_model, property));
                }
            }
 
            return true;
        }
 
        public void Dispose()
        {
        }
 
        public void Reset()
        {
            throw new NotImplementedException();
        }
 
        private static object? GetModel(object container, ModelMetadata property)
        {
            return property.PropertyGetter!(container);
        }
    }
}