diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs
index fa1b482f708ae61ddcac55b09511e4913de62b13..bd2caaa62542f66d2797c0d53a8fc21f1c96fc37 100644
--- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs
+++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq.Expressions;
 using System.Reflection;
 using System.Threading.Tasks;
@@ -17,6 +18,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
     /// </summary>
     public class ComplexTypeModelBinder : IModelBinder
     {
+        // Don't want a new public enum because communication between the private and internal methods of this class
+        // should not be exposed. Can't use an internal enum because types of [TheoryData] values must be public.
+
+        // Model contains only properties that are expected to bind from value providers and no value provider has
+        // matching data.
+        internal const int NoDataAvailable = 0;
+        // If model contains properties that are expected to bind from value providers, no value provider has matching
+        // data. Remaining (greedy) properties might bind successfully.
+        internal const int GreedyPropertiesMayHaveData = 1;
+        // Model contains at least one property that is expected to bind from value providers and a value provider has
+        // matching data.
+        internal const int ValueProviderDataAvailable = 2;
+
         private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
         private readonly ILogger _logger;
         private Func<object> _modelCreator;
@@ -76,18 +90,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
 
             _logger.AttemptingToBindModel(bindingContext);
 
-            if (!CanCreateModel(bindingContext))
+            var propertyData = CanCreateModel(bindingContext);
+            if (propertyData == NoDataAvailable)
             {
                 return Task.CompletedTask;
             }
 
             // Perf: separated to avoid allocating a state machine when we don't
             // need to go async.
-            return BindModelCoreAsync(bindingContext);
+            return BindModelCoreAsync(bindingContext, propertyData);
         }
 
-        private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
+        private async Task BindModelCoreAsync(ModelBindingContext bindingContext, int propertyData)
         {
+            Debug.Assert(propertyData == GreedyPropertiesMayHaveData || propertyData == ValueProviderDataAvailable);
+
             // Create model first (if necessary) to avoid reporting errors about properties when activation fails.
             if (bindingContext.Model == null)
             {
@@ -96,6 +113,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
 
             var modelMetadata = bindingContext.ModelMetadata;
             var attemptedPropertyBinding = false;
+            var propertyBindingSucceeded = false;
+            var postponePlaceholderBinding = false;
             for (var i = 0; i < modelMetadata.Properties.Count; i++)
             {
                 var property = modelMetadata.Properties[i];
@@ -104,42 +123,62 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
                     continue;
                 }
 
-                // Pass complex (including collection) values down so that binding system does not unnecessarily
-                // recreate instances or overwrite inner properties that are not bound. No need for this with simple
-                // values because they will be overwritten if binding succeeds. Arrays are never reused because they
-                // cannot be resized.
-                object propertyModel = null;
-                if (property.PropertyGetter != null &&
-                    property.IsComplexType &&
-                    !property.ModelType.IsArray)
+                if (_propertyBinders[property] is PlaceholderBinder)
                 {
-                    propertyModel = property.PropertyGetter(bindingContext.Model);
+                    if (postponePlaceholderBinding)
+                    {
+                        // Decided to postpone binding properties that complete a loop in the model types when handling
+                        // an earlier loop-completing property. Postpone binding this property too.
+                        continue;
+                    }
+                    else if (!bindingContext.IsTopLevelObject &&
+                        !propertyBindingSucceeded &&
+                        propertyData == GreedyPropertiesMayHaveData)
+                    {
+                        // Have no confirmation of data for the current instance. Postpone completing the loop until
+                        // we _know_ the current instance is useful. Recursion would otherwise occur prior to the
+                        // block with a similar condition after the loop.
+                        //
+                        // Example cases include an Employee class containing
+                        // 1. a Manager property of type Employee
+                        // 2. an Employees property of type IList<Employee>
+                        postponePlaceholderBinding = true;
+                        continue;
+                    }
                 }
 
                 var fieldName = property.BinderModelName ?? property.PropertyName;
                 var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
-
-                ModelBindingResult result;
-                using (bindingContext.EnterNestedScope(
-                    modelMetadata: property,
-                    fieldName: fieldName,
-                    modelName: modelName,
-                    model: propertyModel))
-                {
-                    await BindProperty(bindingContext);
-                    result = bindingContext.Result;
-                }
+                var result = await BindProperty(bindingContext, property, fieldName, modelName);
 
                 if (result.IsModelSet)
                 {
                     attemptedPropertyBinding = true;
-                    SetProperty(bindingContext, modelName, property, result);
+                    propertyBindingSucceeded = true;
                 }
                 else if (property.IsBindingRequired)
                 {
                     attemptedPropertyBinding = true;
-                    var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
-                    bindingContext.ModelState.TryAddModelError(modelName, message);
+                }
+            }
+
+            if (postponePlaceholderBinding && propertyBindingSucceeded)
+            {
+                // Have some data for this instance. Continue with the model type loop.
+                for (var i = 0; i < modelMetadata.Properties.Count; i++)
+                {
+                    var property = modelMetadata.Properties[i];
+                    if (!CanBindProperty(bindingContext, property))
+                    {
+                        continue;
+                    }
+
+                    if (_propertyBinders[property] is PlaceholderBinder)
+                    {
+                        var fieldName = property.BinderModelName ?? property.PropertyName;
+                        var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);
+                        await BindProperty(bindingContext, property, fieldName, modelName);
+                    }
                 }
             }
 
@@ -157,8 +196,37 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
                 bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
             }
 
-            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
             _logger.DoneAttemptingToBindModel(bindingContext);
+
+            // Have all binders failed because no data was available?
+            //
+            // If CanCreateModel determined a property has data, failures are likely due to conversion errors. For
+            // example, user may submit ?[0].id=twenty&[1].id=twenty-one&[2].id=22 for a collection of a complex type
+            // with an int id property. In that case, the bound model should be [ {}, {}, { id = 22 }] and
+            // ModelState should contain errors about both [0].id and [1].id. Do not inform higher-level binders of the
+            // failure in this and similar cases.
+            //
+            // If CanCreateModel could not find data for non-greedy properties, failures indicate greedy binders were
+            // unsuccessful. For example, user may submit file attachments [0].File and [1].File but not [2].File for
+            // a collection of a complex type containing an IFormFile property. In that case, we have exhausted the
+            // attached files and checking for [3].File is likely be pointless. (And, if it had a point, would we stop
+            // after 10 failures, 100, or more -- all adding redundant errors to ModelState?) Inform higher-level
+            // binders of the failure.
+            //
+            // Required properties do not change the logic below. Missed required properties cause ModelState errors
+            // but do not necessarily prevent further attempts to bind.
+            //
+            // This logic is intended to maximize correctness but does not avoid infinite loops or recursion when a
+            // greedy model binder succeeds unconditionally.
+            if (!bindingContext.IsTopLevelObject &&
+                !propertyBindingSucceeded &&
+                propertyData == GreedyPropertiesMayHaveData)
+            {
+                bindingContext.Result = ModelBindingResult.Failed();
+                return;
+            }
+
+            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
         }
 
         /// <summary>
@@ -194,6 +262,48 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             return true;
         }
 
+        private async Task<ModelBindingResult> BindProperty(
+            ModelBindingContext bindingContext,
+            ModelMetadata property,
+            string fieldName,
+            string modelName)
+        {
+            // Pass complex (including collection) values down so that binding system does not unnecessarily
+            // recreate instances or overwrite inner properties that are not bound. No need for this with simple
+            // values because they will be overwritten if binding succeeds. Arrays are never reused because they
+            // cannot be resized.
+            object propertyModel = null;
+            if (property.PropertyGetter != null &&
+                property.IsComplexType &&
+                !property.ModelType.IsArray)
+            {
+                propertyModel = property.PropertyGetter(bindingContext.Model);
+            }
+
+            ModelBindingResult result;
+            using (bindingContext.EnterNestedScope(
+                modelMetadata: property,
+                fieldName: fieldName,
+                modelName: modelName,
+                model: propertyModel))
+            {
+                await BindProperty(bindingContext);
+                result = bindingContext.Result;
+            }
+
+            if (result.IsModelSet)
+            {
+                SetProperty(bindingContext, modelName, property, result);
+            }
+            else if (property.IsBindingRequired)
+            {
+                var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
+                bindingContext.ModelState.TryAddModelError(modelName, message);
+            }
+
+            return result;
+        }
+
         /// <summary>
         /// Attempts to bind a property of the model.
         /// </summary>
@@ -208,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             return binder.BindModelAsync(bindingContext);
         }
 
-        internal bool CanCreateModel(ModelBindingContext bindingContext)
+        internal int CanCreateModel(ModelBindingContext bindingContext)
         {
             var isTopLevelObject = bindingContext.IsTopLevelObject;
 
@@ -227,33 +337,28 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var bindingSource = bindingContext.BindingSource;
             if (!isTopLevelObject && bindingSource != null && bindingSource.IsGreedy)
             {
-                return false;
+                return NoDataAvailable;
             }
 
             // Create the object if:
             // 1. It is a top level model.
             if (isTopLevelObject)
             {
-                return true;
+                return ValueProviderDataAvailable;
             }
 
             // 2. Any of the model properties can be bound.
-            if (CanBindAnyModelProperties(bindingContext))
-            {
-                return true;
-            }
-
-            return false;
+            return CanBindAnyModelProperties(bindingContext);
         }
 
-        private bool CanBindAnyModelProperties(ModelBindingContext bindingContext)
+        private int CanBindAnyModelProperties(ModelBindingContext bindingContext)
         {
             // If there are no properties on the model, there is nothing to bind. We are here means this is not a top
             // level object. So we return false.
             if (bindingContext.ModelMetadata.Properties.Count == 0)
             {
                 _logger.NoPublicSettableProperties(bindingContext);
-                return false;
+                return NoDataAvailable;
             }
 
             // We want to check to see if any of the properties of the model can be bound using the value providers or
@@ -279,7 +384,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             // Bottom line, if any property meets the above conditions and has a value from ValueProviders, then we'll
             // create the model and try to bind it. Of, if ANY properties of the model have a greedy source,
             // then we go ahead and create it.
-            //
+            var hasGreedyBinders = false;
             for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++)
             {
                 var propertyMetadata = bindingContext.ModelMetadata.Properties[i];
@@ -292,7 +397,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
                 var bindingSource = propertyMetadata.BindingSource;
                 if (bindingSource != null && bindingSource.IsGreedy)
                 {
-                    return true;
+                    hasGreedyBinders = true;
+                    continue;
                 }
 
                 // Otherwise, check whether the (perhaps filtered) value providers have a match.
@@ -307,14 +413,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
                     // If any property can be bound from a value provider, then success.
                     if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                     {
-                        return true;
+                        return ValueProviderDataAvailable;
                     }
                 }
             }
 
+            if (hasGreedyBinders)
+            {
+                return GreedyPropertiesMayHaveData;
+            }
+
             _logger.CannotBindToComplexType(bindingContext);
 
-            return false;
+            return NoDataAvailable;
         }
 
         // Internal for tests
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
index 8774ba896f5906db1c0180b51d9bb372e2decfae..d8f282c337bdc98a23bdb520cc602ab1a4763a7e 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs
@@ -24,11 +24,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
         private static readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
 
         [Theory]
-        [InlineData(true, true)]
-        [InlineData(false, false)]
-        public void CanCreateModel_ReturnsTrue_IfIsTopLevelObject(
-            bool isTopLevelObject,
-            bool expectedCanCreate)
+        [InlineData(true, ComplexTypeModelBinder.ValueProviderDataAvailable)]
+        [InlineData(false, ComplexTypeModelBinder.NoDataAvailable)]
+        public void CanCreateModel_ReturnsTrue_IfIsTopLevelObject(bool isTopLevelObject, int expectedCanCreate)
         {
             var bindingContext = CreateContext(GetMetadataForType(typeof(Person)));
             bindingContext.IsTopLevelObject = isTopLevelObject;
@@ -56,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.False(canCreate);
+            Assert.Equal(ComplexTypeModelBinder.NoDataAvailable, canCreate);
         }
 
         [Fact]
@@ -71,16 +69,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.True(canCreate);
+            Assert.Equal(ComplexTypeModelBinder.ValueProviderDataAvailable, canCreate);
         }
 
         [Theory]
-        [InlineData(true)]
-        [InlineData(false)]
-        public void CanCreateModel_CreatesModel_WithAllGreedyProperties(bool isTopLevelObject)
+        [InlineData(ComplexTypeModelBinder.ValueProviderDataAvailable)]
+        [InlineData(ComplexTypeModelBinder.GreedyPropertiesMayHaveData)]
+        public void CanCreateModel_CreatesModel_WithAllGreedyProperties(int expectedCanCreate)
         {
             var bindingContext = CreateContext(GetMetadataForType(typeof(HasAllGreedyProperties)));
-            bindingContext.IsTopLevelObject = isTopLevelObject;
+            bindingContext.IsTopLevelObject = expectedCanCreate == ComplexTypeModelBinder.ValueProviderDataAvailable;
 
             var binder = CreateBinder(bindingContext.ModelMetadata);
 
@@ -88,20 +86,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.True(canCreate);
+            Assert.Equal(expectedCanCreate, canCreate);
         }
 
         [Theory]
-        [InlineData(true)]
-        [InlineData(false)]
-        public void CanCreateModel_ReturnsTrue_IfNotIsTopLevelObject_BasedOnValueAvailability(
-            bool valueAvailable)
+        [InlineData(ComplexTypeModelBinder.ValueProviderDataAvailable)]
+        [InlineData(ComplexTypeModelBinder.NoDataAvailable)]
+        public void CanCreateModel_ReturnsTrue_IfNotIsTopLevelObject_BasedOnValueAvailability(int valueAvailable)
         {
             // Arrange
             var valueProvider = new Mock<IValueProvider>(MockBehavior.Strict);
             valueProvider
                 .Setup(provider => provider.ContainsPrefix("SimpleContainer.Simple.Name"))
-                .Returns(valueAvailable);
+                .Returns(valueAvailable == ComplexTypeModelBinder.ValueProviderDataAvailable);
 
             var modelMetadata = GetMetadataForProperty(typeof(SimpleContainer), nameof(SimpleContainer.Simple));
             var bindingContext = CreateContext(modelMetadata);
@@ -133,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.False(canCreate);
+            Assert.Equal(ComplexTypeModelBinder.NoDataAvailable, canCreate);
         }
 
         [Fact]
@@ -149,20 +146,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.True(canCreate);
+            Assert.Equal(ComplexTypeModelBinder.ValueProviderDataAvailable, canCreate);
         }
 
         [Theory]
-        [InlineData(typeof(TypeWithNoBinderMetadata), false)]
-        [InlineData(typeof(TypeWithNoBinderMetadata), true)]
+        [InlineData(typeof(TypeWithNoBinderMetadata), ComplexTypeModelBinder.NoDataAvailable)]
+        [InlineData(typeof(TypeWithNoBinderMetadata), ComplexTypeModelBinder.ValueProviderDataAvailable)]
         public void CanCreateModel_CreatesModelForValueProviderBasedBinderMetadatas_IfAValueProviderProvidesValue(
             Type modelType,
-            bool valueProviderProvidesValue)
+            int valueProviderProvidesValue)
         {
             var valueProvider = new Mock<IValueProvider>();
             valueProvider
                 .Setup(o => o.ContainsPrefix(It.IsAny<string>()))
-                .Returns(valueProviderProvidesValue);
+                .Returns(valueProviderProvidesValue == ComplexTypeModelBinder.ValueProviderDataAvailable);
 
             var bindingContext = CreateContext(GetMetadataForType(modelType));
             bindingContext.IsTopLevelObject = false;
@@ -179,18 +176,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
         }
 
         [Theory]
-        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)]
-        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)]
-        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)]
-        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)]
+        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), ComplexTypeModelBinder.GreedyPropertiesMayHaveData)]
+        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), ComplexTypeModelBinder.ValueProviderDataAvailable)]
+        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), ComplexTypeModelBinder.GreedyPropertiesMayHaveData)]
+        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), ComplexTypeModelBinder.ValueProviderDataAvailable)]
         public void CanCreateModel_CreatesModelForValueProviderBasedBinderMetadatas_IfPropertyHasGreedyBindingSource(
             Type modelType,
-            bool valueProviderProvidesValue)
+            int expectedCanCreate)
         {
             var valueProvider = new Mock<IValueProvider>();
             valueProvider
                 .Setup(o => o.ContainsPrefix(It.IsAny<string>()))
-                .Returns(valueProviderProvidesValue);
+                .Returns(expectedCanCreate == ComplexTypeModelBinder.ValueProviderDataAvailable);
 
             var bindingContext = CreateContext(GetMetadataForType(modelType));
             bindingContext.IsTopLevelObject = false;
@@ -203,15 +200,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.True(canCreate);
+            Assert.Equal(expectedCanCreate, canCreate);
         }
 
         [Theory]
-        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)]
-        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)]
+        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), ComplexTypeModelBinder.GreedyPropertiesMayHaveData)]
+        [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), ComplexTypeModelBinder.ValueProviderDataAvailable)]
         public void CanCreateModel_ForExplicitValueProviderMetadata_UsesOriginalValueProvider(
             Type modelType,
-            bool originalValueProviderProvidesValue)
+            int expectedCanCreate)
         {
             var valueProvider = new Mock<IValueProvider>();
             valueProvider
@@ -221,7 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var originalValueProvider = new Mock<IBindingSourceValueProvider>();
             originalValueProvider
                 .Setup(o => o.ContainsPrefix(It.IsAny<string>()))
-                .Returns(originalValueProviderProvidesValue);
+                .Returns(expectedCanCreate == ComplexTypeModelBinder.ValueProviderDataAvailable);
 
             originalValueProvider
                 .Setup(o => o.Filter(It.IsAny<BindingSource>()))
@@ -238,18 +235,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             var canCreate = binder.CanCreateModel(bindingContext);
 
             // Assert
-            Assert.True(canCreate);
+            Assert.Equal(expectedCanCreate, canCreate);
         }
 
         [Theory]
-        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false, true)]
-        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true, true)]
-        [InlineData(typeof(TypeWithNoBinderMetadata), false, false)]
-        [InlineData(typeof(TypeWithNoBinderMetadata), true, true)]
+        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false, ComplexTypeModelBinder.GreedyPropertiesMayHaveData)]
+        [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true, ComplexTypeModelBinder.ValueProviderDataAvailable)]
+        [InlineData(typeof(TypeWithNoBinderMetadata), false, ComplexTypeModelBinder.NoDataAvailable)]
+        [InlineData(typeof(TypeWithNoBinderMetadata), true, ComplexTypeModelBinder.ValueProviderDataAvailable)]
         public void CanCreateModel_UnmarkedProperties_UsesCurrentValueProvider(
             Type modelType,
             bool valueProviderProvidesValue,
-            bool expectedCanCreate)
+            int expectedCanCreate)
         {
             var valueProvider = new Mock<IValueProvider>();
             valueProvider
@@ -602,8 +599,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
             // Arrange
             var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), new Person());
             var originalModel = bindingContext.Model;
+            var binders = bindingContext.ModelMetadata.Properties.ToDictionary(
+                keySelector: item => item,
+                elementSelector: item => (IModelBinder)null);
 
-            var binder = new Mock<TestableComplexTypeModelBinder>() { CallBase = true };
+            var binder = new Mock<TestableComplexTypeModelBinder>(binders) { CallBase = true };
             binder
                 .Setup(b => b.CreateModelPublic(It.IsAny<ModelBindingContext>()))
                 .Verifiable();
@@ -621,8 +621,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
         {
             // Arrange
             var bindingContext = CreateContext(GetMetadataForType(typeof(Person)), model: null);
+            var binders = bindingContext.ModelMetadata.Properties.ToDictionary(
+                keySelector: item => item,
+                elementSelector: item => (IModelBinder)null);
 
-            var testableBinder = new Mock<TestableComplexTypeModelBinder> { CallBase = true };
+            var testableBinder = new Mock<TestableComplexTypeModelBinder>(binders) { CallBase = true };
             testableBinder
                 .Setup(o => o.CreateModelPublic(bindingContext))
                 .Returns(new Person())
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs
index beffb6d9a1ac47c841c2e3cc96562a19e8dcf996..72219b7ad8ce97932bc9cbcd09e51bf2975263d8 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs
@@ -3141,6 +3141,276 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
             Assert.Equal("10,20", entry.RawValue);
         }
 
+        private class Person5
+        {
+            public string Name { get; set; }
+            public IFormFile Photo { get; set; }
+        }
+
+        // Regression test for #4802.
+        [Fact]
+        public async Task ComplexTypeModelBinder_ReportsFailureToCollectionModelBinder()
+        {
+            // Arrange
+            var parameter = new ParameterDescriptor()
+            {
+                Name = "parameter",
+                ParameterType = typeof(IList<Person5>),
+            };
+
+            var testContext = ModelBindingTestHelper.GetTestContext(request =>
+            {
+                SetFormFileBodyContent(request, "Hello world!", "[0].Photo");
+
+                // CollectionModelBinder binds an empty collection when value providers are all empty.
+                request.QueryString = new QueryString("?a=b");
+            });
+
+            var modelState = testContext.ModelState;
+            var metadata = GetMetadata(testContext, parameter);
+            var modelBinder = GetModelBinder(testContext, parameter, metadata);
+            var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
+            var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
+
+            // Act
+            var modelBindingResult = await parameterBinder.BindModelAsync(
+                testContext,
+                modelBinder,
+                valueProvider,
+                parameter,
+                metadata,
+                value: null);
+
+            // Assert
+            Assert.True(modelBindingResult.IsModelSet);
+
+            var model = Assert.IsType<List<Person5>>(modelBindingResult.Model);
+            var person = Assert.Single(model);
+            Assert.Null(person.Name);
+            Assert.NotNull(person.Photo);
+            using (var reader = new StreamReader(person.Photo.OpenReadStream()))
+            {
+                Assert.Equal("Hello world!", await reader.ReadToEndAsync());
+            }
+
+            Assert.True(modelState.IsValid);
+            var state = Assert.Single(modelState);
+            Assert.Equal("[0].Photo", state.Key);
+            Assert.Null(state.Value.AttemptedValue);
+            Assert.Empty(state.Value.Errors);
+            Assert.Null(state.Value.RawValue);
+        }
+
+        private class Person6
+        {
+            public string Name { get; set; }
+
+            public Person6 Mother { get; set; }
+
+            public IFormFile Photo { get; set; }
+        }
+
+        // Regression test for #6616.
+        [Fact]
+        public async Task ComplexTypeModelBinder_ReportsFailureToComplexTypeModelBinder_NearTopLevel()
+        {
+            // Arrange
+            var parameter = new ParameterDescriptor()
+            {
+                Name = "parameter",
+                ParameterType = typeof(Person6),
+            };
+
+            var testContext = ModelBindingTestHelper.GetTestContext(request =>
+            {
+                SetFormFileBodyContent(request, "Hello world!", "Photo");
+            });
+
+            var modelState = testContext.ModelState;
+            var metadata = GetMetadata(testContext, parameter);
+            var modelBinder = GetModelBinder(testContext, parameter, metadata);
+            var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
+            var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
+
+            // Act
+            var modelBindingResult = await parameterBinder.BindModelAsync(
+                testContext,
+                modelBinder,
+                valueProvider,
+                parameter,
+                metadata,
+                value: null);
+
+            // Assert
+            Assert.True(modelBindingResult.IsModelSet);
+
+            var model = Assert.IsType<Person6>(modelBindingResult.Model);
+            Assert.Null(model.Mother);
+            Assert.Null(model.Name);
+            Assert.NotNull(model.Photo);
+            using (var reader = new StreamReader(model.Photo.OpenReadStream()))
+            {
+                Assert.Equal("Hello world!", await reader.ReadToEndAsync());
+            }
+
+            Assert.True(modelState.IsValid);
+            var state = Assert.Single(modelState);
+            Assert.Equal("Photo", state.Key);
+            Assert.Null(state.Value.AttemptedValue);
+            Assert.Empty(state.Value.Errors);
+            Assert.Null(state.Value.RawValue);
+        }
+
+        // Regression test for #6616.
+        [Fact]
+        public async Task ComplexTypeModelBinder_ReportsFailureToComplexTypeModelBinder()
+        {
+            // Arrange
+            var parameter = new ParameterDescriptor()
+            {
+                Name = "parameter",
+                ParameterType = typeof(Person6),
+            };
+
+            var testContext = ModelBindingTestHelper.GetTestContext(request =>
+            {
+                SetFormFileBodyContent(request, "Hello world!", "Photo");
+                SetFormFileBodyContent(request, "Hello Mom!", "Mother.Photo");
+            });
+
+            var modelState = testContext.ModelState;
+            var metadata = GetMetadata(testContext, parameter);
+            var modelBinder = GetModelBinder(testContext, parameter, metadata);
+            var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
+            var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
+
+            // Act
+            var modelBindingResult = await parameterBinder.BindModelAsync(
+                testContext,
+                modelBinder,
+                valueProvider,
+                parameter,
+                metadata,
+                value: null);
+
+            // Assert
+            Assert.True(modelBindingResult.IsModelSet);
+
+            var model = Assert.IsType<Person6>(modelBindingResult.Model);
+            Assert.NotNull(model.Mother);
+            Assert.Null(model.Mother.Mother);
+            Assert.NotNull(model.Mother.Photo);
+            using (var reader = new StreamReader(model.Mother.Photo.OpenReadStream()))
+            {
+                Assert.Equal("Hello Mom!", await reader.ReadToEndAsync());
+            }
+
+            Assert.Null(model.Name);
+            Assert.NotNull(model.Photo);
+            using (var reader = new StreamReader(model.Photo.OpenReadStream()))
+            {
+                Assert.Equal("Hello world!", await reader.ReadToEndAsync());
+            }
+
+            Assert.True(modelState.IsValid);
+            Assert.Collection(
+                modelState,
+                kvp =>
+                {
+                    Assert.Equal("Photo", kvp.Key);
+                    Assert.Null(kvp.Value.AttemptedValue);
+                    Assert.Empty(kvp.Value.Errors);
+                    Assert.Null(kvp.Value.RawValue);
+                },
+                kvp =>
+                {
+                    Assert.Equal("Mother.Photo", kvp.Key);
+                    Assert.Null(kvp.Value.AttemptedValue);
+                    Assert.Empty(kvp.Value.Errors);
+                    Assert.Null(kvp.Value.RawValue);
+                });
+        }
+
+        private class Person7
+        {
+            public string Name { get; set; }
+
+            public IList<Person7> Children { get; set; }
+
+            public IFormFile Photo { get; set; }
+        }
+
+        // Regression test for #6616.
+        [Fact]
+        public async Task ComplexTypeModelBinder_ReportsFailureToComplexTypeModelBinder_ViaCollection()
+        {
+            // Arrange
+            var parameter = new ParameterDescriptor()
+            {
+                Name = "parameter",
+                ParameterType = typeof(Person7),
+            };
+
+            var testContext = ModelBindingTestHelper.GetTestContext(request =>
+            {
+                SetFormFileBodyContent(request, "Hello world!", "Photo");
+                SetFormFileBodyContent(request, "Hello Fred!", "Children[0].Photo");
+                SetFormFileBodyContent(request, "Hello Ginger!", "Children[1].Photo");
+
+                request.QueryString = new QueryString("?Children[0].Name=Fred&Children[1].Name=Ginger");
+            });
+
+            var modelState = testContext.ModelState;
+            var metadata = GetMetadata(testContext, parameter);
+            var modelBinder = GetModelBinder(testContext, parameter, metadata);
+            var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
+            var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext);
+
+            // Act
+            var modelBindingResult = await parameterBinder.BindModelAsync(
+                testContext,
+                modelBinder,
+                valueProvider,
+                parameter,
+                metadata,
+                value: null);
+
+            // Assert
+            Assert.True(modelBindingResult.IsModelSet);
+
+            var model = Assert.IsType<Person7>(modelBindingResult.Model);
+            Assert.NotNull(model.Children);
+            Assert.Collection(
+                model.Children,
+                item =>
+                {
+                    Assert.Null(item.Children);
+                    Assert.Equal("Fred", item.Name);
+                    using (var reader = new StreamReader(item.Photo.OpenReadStream()))
+                    {
+                        Assert.Equal("Hello Fred!", reader.ReadToEnd());
+                    }
+                },
+                item =>
+                {
+                    Assert.Null(item.Children);
+                    Assert.Equal("Ginger", item.Name);
+                    using (var reader = new StreamReader(item.Photo.OpenReadStream()))
+                    {
+                        Assert.Equal("Hello Ginger!", reader.ReadToEnd());
+                    }
+                });
+
+            Assert.Null(model.Name);
+            Assert.NotNull(model.Photo);
+            using (var reader = new StreamReader(model.Photo.OpenReadStream()))
+            {
+                Assert.Equal("Hello world!", await reader.ReadToEndAsync());
+            }
+
+            Assert.True(modelState.IsValid);
+        }
+
         private static void SetJsonBodyContent(HttpRequest request, string content)
         {
             var stream = new MemoryStream(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(content));
@@ -3151,18 +3421,32 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
         private static void SetFormFileBodyContent(HttpRequest request, string content, string name)
         {
             const string fileName = "text.txt";
-            var fileCollection = new FormFileCollection();
-            var formCollection = new FormCollection(new Dictionary<string, StringValues>(), fileCollection);
-            var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content));
 
-            request.Form = formCollection;
-            request.ContentType = "multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq";
-            request.Headers["Content-Disposition"] = $"form-data; name={name}; filename={fileName}";
+            FormFileCollection fileCollection;
+            if (request.HasFormContentType)
+            {
+                // Do less work and do not overwrite previous information if called a second time.
+                fileCollection = (FormFileCollection)request.Form.Files;
+            }
+            else
+            {
+                fileCollection = new FormFileCollection();
+                var formCollection = new FormCollection(new Dictionary<string, StringValues>(), fileCollection);
+
+                request.ContentType = "multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq";
+                request.Form = formCollection;
+            }
 
-            fileCollection.Add(new FormFile(memoryStream, 0, memoryStream.Length, name, fileName)
+            var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content));
+            var file = new FormFile(memoryStream, 0, memoryStream.Length, name, fileName)
             {
-                Headers = request.Headers
-            });
+                Headers = new HeaderDictionary(),
+
+                // Do not move this up. Headers must be non-null before the ContentDisposition property is accessed.
+                ContentDisposition = $"form-data; name={name}; filename={fileName}",
+            };
+
+            fileCollection.Add(file);
         }
 
         private ModelMetadata GetMetadata(ModelBindingTestContext context, ParameterDescriptor parameter)
diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs
index fb89e0e2049e10b423edec515144c9147a7d1281..a5440e5de2d0751c76c1e8b4dd77d647c3e27271 100644
--- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs
+++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs
@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
-using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Linq;
 using System.Threading.Tasks;
@@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
         private class Address
         {
             [FromHeader(Name = "Header")]
-            [Required]
+            [BindRequired]
             public string Street { get; set; }
         }
 
@@ -33,6 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
         public async Task BindPropertyFromHeader_NoData_UsesFullPathAsKeyForModelStateErrors()
         {
             // Arrange
+            var expected = "A value for the 'Header' parameter or property was not provided.";
             var parameter = new ParameterDescriptor()
             {
                 Name = "Parameter1",
@@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
             var key = Assert.Single(modelState.Keys);
             Assert.Equal("CustomParameter.Address.Header", key);
             var error = Assert.Single(modelState[key].Errors);
-            Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Street"), error.ErrorMessage);
+            Assert.Equal(expected, error.ErrorMessage);
         }
 
         [Fact]
@@ -277,7 +277,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
                 ParameterType = modelType
             };
 
-            Action<HttpRequest> action = r => r.Headers.Add("CustomParameter", new[] { expectedAttemptedValue });
+            void action(HttpRequest r) => r.Headers.Add("CustomParameter", new[] { expectedAttemptedValue });
             var testContext = GetModelBindingTestContext(action);
             var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);