Beispiel #1
0
        /// <summary>
        /// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>.
        /// </summary>
        /// <param name="attributes">A collection of attributes which are used to construct <see cref="BindingInfo"/>
        /// </param>
        /// <returns>A new instance of <see cref="BindingInfo"/>.</returns>
        public static BindingInfo GetBindingInfo(IEnumerable<object> attributes)
        {
            var bindingInfo = new BindingInfo();
            var isBindingInfoPresent = false;

            // BinderModelName
            foreach (var binderModelNameAttribute in attributes.OfType<IModelNameProvider>())
            {
                isBindingInfoPresent = true;
                if (binderModelNameAttribute?.Name != null)
                {
                    bindingInfo.BinderModelName = binderModelNameAttribute.Name;
                    break;
                }
            }

            // BinderType
            foreach (var binderTypeAttribute in attributes.OfType<IBinderTypeProviderMetadata>())
            {
                isBindingInfoPresent = true;
                if (binderTypeAttribute.BinderType != null)
                {
                    bindingInfo.BinderType = binderTypeAttribute.BinderType;
                    break;
                }
            }

            // BindingSource
            foreach (var bindingSourceAttribute in attributes.OfType<IBindingSourceMetadata>())
            {
                isBindingInfoPresent = true;
                if (bindingSourceAttribute.BindingSource != null)
                {
                    bindingInfo.BindingSource = bindingSourceAttribute.BindingSource;
                    break;
                }
            }

            // PropertyFilterProvider
            var propertyFilterProviders = attributes.OfType<IPropertyFilterProvider>().ToArray();
            if (propertyFilterProviders.Length == 1)
            {
                isBindingInfoPresent = true;
                bindingInfo.PropertyFilterProvider = propertyFilterProviders[0];
            }
            else if (propertyFilterProviders.Length > 1)
            {
                isBindingInfoPresent = true;
                bindingInfo.PropertyFilterProvider = new CompositePropertyFilterProvider(propertyFilterProviders);
            }

            return isBindingInfoPresent ? bindingInfo : null;
        }
 public TestModelBinderProviderContext(Type modelType)
 {
     Metadata = CachedMetadataProvider.GetMetadataForType(modelType);
     MetadataProvider = CachedMetadataProvider;
     BindingInfo = new BindingInfo()
     {
         BinderModelName = Metadata.BinderModelName,
         BinderType = Metadata.BinderType,
         BindingSource = Metadata.BindingSource,
         PropertyFilterProvider = Metadata.PropertyFilterProvider,
     };
 }
Beispiel #3
0
        /// <summary>
        /// Creates a copy of a <see cref="BindingInfo"/>.
        /// </summary>
        /// <param name="other">The <see cref="BindingInfo"/> to copy.</param>
        public BindingInfo(BindingInfo other)
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            BindingSource = other.BindingSource;
            BinderModelName = other.BinderModelName;
            BinderType = other.BinderType;
            PropertyFilterProvider = other.PropertyFilterProvider;
        }
        public TestModelBinderProviderContext(ModelMetadata metadata, BindingInfo bindingInfo)
        {
            Metadata = metadata;
            BindingInfo = bindingInfo ?? new BindingInfo
            {
                BinderModelName = metadata.BinderModelName,
                BinderType = metadata.BinderType,
                BindingSource = metadata.BindingSource,
                PropertyFilterProvider = metadata.PropertyFilterProvider,
            };

            MetadataProvider = CachedMetadataProvider;
        }
        /// <summary>
        /// Creates a new <see cref="DefaultModelBindingContext"/> for top-level model binding operation.
        /// </summary>
        /// <param name="operationBindingContext">
        /// The <see cref="OperationBindingContext"/> associated with the binding operation.
        /// </param>
        /// <param name="metadata"><see cref="ModelMetadata"/> associated with the model.</param>
        /// <param name="bindingInfo"><see cref="BindingInfo"/> associated with the model.</param>
        /// <param name="modelName">The name of the property or parameter being bound.</param>
        /// <returns>A new instance of <see cref="DefaultModelBindingContext"/>.</returns>
        public static ModelBindingContext CreateBindingContext(
            OperationBindingContext operationBindingContext,
            ModelMetadata metadata,
            BindingInfo bindingInfo,
            string modelName)
        {
            if (operationBindingContext == null)
            {
                throw new ArgumentNullException(nameof(operationBindingContext));
            }

            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            if (modelName == null)
            {
                throw new ArgumentNullException(nameof(modelName));
            }

            var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName;
            var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider;

            var valueProvider = operationBindingContext.ValueProvider;
            var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource;
            if (bindingSource != null && !bindingSource.IsGreedy)
            {
                valueProvider = FilterValueProvider(operationBindingContext.ValueProvider, bindingSource);
            }

            return new DefaultModelBindingContext()
            {
                BinderModelName = binderModelName,
                BindingSource = bindingSource,
                PropertyFilter = propertyFilterProvider?.PropertyFilter,

                // Because this is the top-level context, FieldName and ModelName should be the same.
                FieldName = binderModelName ?? modelName,
                ModelName = binderModelName ?? modelName,

                IsTopLevelObject = true,
                ModelMetadata = metadata,
                ModelState = operationBindingContext.ActionContext.ModelState,
                OperationBindingContext = operationBindingContext,
                ValueProvider = valueProvider,

                ValidationState = new ValidationStateDictionary(),
            };
        }
Beispiel #6
0
        public void GetBindingInfo_WithAttributesAndModelMetadata_UsesPropertyPredicateProviderFromModelMetadata_WhenNotFoundViaAttributes()
        {
            // Arrange
            var attributes             = new object[] { new ModelBinderAttribute(typeof(object)), new ControllerAttribute(), new BindNeverAttribute(), };
            var propertyFilterProvider = Mock.Of <IPropertyFilterProvider>();
            var modelType = typeof(Guid);
            var provider  = new TestModelMetadataProvider();

            provider.ForType(modelType).BindingDetails(metadata =>
            {
                metadata.PropertyFilterProvider = propertyFilterProvider;
            });
            var modelMetadata = provider.GetMetadataForType(modelType);

            // Act
            var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);

            // Assert
            Assert.NotNull(bindingInfo);
            Assert.Same(propertyFilterProvider, bindingInfo.PropertyFilterProvider);
        }
Beispiel #7
0
            public override IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo)
            {
                if (metadata == null)
                {
                    throw new ArgumentNullException(nameof(metadata));
                }

                if (bindingInfo == null)
                {
                    throw new ArgumentNullException(nameof(bindingInfo));
                }

                // For non-root nodes we use the ModelMetadata as the cache token. This ensures that all non-root
                // nodes with the same metadata will have the same binder. This is OK because for an non-root
                // node there's no opportunity to customize binding info like there is for a parameter.
                var token = metadata;

                var nestedContext = new DefaultModelBinderProviderContext(this, metadata, bindingInfo);

                return(_factory.CreateBinderCoreCached(nestedContext, token));
            }
            public DefaultModelBinderProviderContext(
                ModelBinderFactory factory,
                ModelBinderFactoryContext factoryContext)
            {
                _factory = factory;
                Metadata = factoryContext.Metadata;
                BindingInfo bindingInfo;
                if (factoryContext.BindingInfo != null)
                {
                    bindingInfo = new BindingInfo(factoryContext.BindingInfo);
                }
                else
                {
                    bindingInfo = new BindingInfo();
                }

                bindingInfo.TryApplyBindingInfo(Metadata);
                BindingInfo = bindingInfo;

                MetadataProvider = _factory._metadataProvider;
                Visited = new Dictionary<Key, IModelBinder?>();
            }
Beispiel #9
0
        public void GetBindingInfo_WithAttributesAndModelMetadata_UsesBinderNameFromModelMetadata_WhenNotFoundViaAttributes()
        {
            // Arrange
            var attributes = new object[] { new ModelBinderAttribute(typeof(object)), new ControllerAttribute(), new BindNeverAttribute(), };
            var modelType  = typeof(Guid);
            var provider   = new TestModelMetadataProvider();

            provider.ForType(modelType).BindingDetails(metadata =>
            {
                metadata.BindingSource   = BindingSource.Special;
                metadata.BinderType      = typeof(string);
                metadata.BinderModelName = "Different";
            });
            var modelMetadata = provider.GetMetadataForType(modelType);

            // Act
            var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata);

            // Assert
            Assert.NotNull(bindingInfo);
            Assert.Same(typeof(object), bindingInfo.BinderType);
            Assert.Same("Different", bindingInfo.BinderModelName);
            Assert.Same(BindingSource.Custom, bindingInfo.BindingSource);
        }
Beispiel #10
0
        /// <summary>
        /// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/> and <paramref name="modelMetadata"/>.
        /// </summary>
        /// <param name="attributes">A collection of attributes which are used to construct <see cref="BindingInfo"/>.</param>
        /// <param name="modelMetadata">The <see cref="ModelMetadata"/>.</param>
        /// <returns>A new instance of <see cref="BindingInfo"/> if any binding metadata was discovered; otherwise or <see langword="null"/>.</returns>
        public static BindingInfo GetBindingInfo(IEnumerable <object> attributes, ModelMetadata modelMetadata)
        {
            if (attributes == null)
            {
                throw new ArgumentNullException(nameof(attributes));
            }

            if (modelMetadata == null)
            {
                throw new ArgumentNullException(nameof(modelMetadata));
            }

            var bindingInfo          = GetBindingInfo(attributes);
            var isBindingInfoPresent = bindingInfo != null;

            if (bindingInfo == null)
            {
                bindingInfo = new BindingInfo();
            }

            isBindingInfoPresent |= bindingInfo.TryApplyBindingInfo(modelMetadata);

            return(isBindingInfoPresent ? bindingInfo : null);
        }
 public override IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo)
 {
     _bindingInfo = bindingInfo;
     return(this.CreateBinder(metadata));
 }
Beispiel #12
0
        public async Task ObsoleteBindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixDoesNotMatch(
            BindingInfo parameterBindingInfo,
            string metadataBinderModelName,
            string parameterName,
            string expectedModelName)
        {
            // Arrange
            var binderExecuted   = false;
            var metadataProvider = new TestModelMetadataProvider();

            metadataProvider.ForType <Person>().BindingDetails(binding =>
            {
                binding.BinderModelName = metadataBinderModelName;
            });

            var metadata    = metadataProvider.GetMetadataForType(typeof(Person));
            var modelBinder = new Mock <IModelBinder>();

            modelBinder
            .Setup(b => b.BindModelAsync(It.IsAny <ModelBindingContext>()))
            .Callback((ModelBindingContext context) =>
            {
                Assert.Equal(expectedModelName, context.ModelName, StringComparer.Ordinal);
            })
            .Returns(Task.CompletedTask);

            var parameterDescriptor = new ParameterDescriptor
            {
                BindingInfo   = parameterBindingInfo,
                Name          = parameterName,
                ParameterType = typeof(Person),
            };

            var factory = new Mock <IModelBinderFactory>(MockBehavior.Strict);

            factory
            .Setup(f => f.CreateBinder(It.IsAny <ModelBinderFactoryContext>()))
            .Callback((ModelBinderFactoryContext context) =>
            {
                binderExecuted = true;
                // Confirm expected data is passed through to ModelBindingFactory.
                Assert.Same(parameterDescriptor.BindingInfo, context.BindingInfo);
                Assert.Same(parameterDescriptor, context.CacheToken);
                Assert.Equal(metadata, context.Metadata);
            })
            .Returns(modelBinder.Object);

            var parameterBinder = new ParameterBinder(
                metadataProvider,
                factory.Object,
                Mock.Of <IObjectModelValidator>(),
                _optionsAccessor,
                NullLoggerFactory.Instance);

            var controllerContext = GetControllerContext();

            // Act & Assert
#pragma warning disable CS0618 // Type or member is obsolete
            await parameterBinder.BindModelAsync(controllerContext, new SimpleValueProvider(), parameterDescriptor);

#pragma warning restore CS0618 // Type or member is obsolete
            Assert.True(binderExecuted);
        }
        public async Task FromServicesOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
        {
            // Arrange
            // Similar to a custom IBindingSourceMetadata implementation or [ModelBinder] subclass on a custom service.
            var metadataProvider = new TestModelMetadataProvider();
            metadataProvider
                .ForType<JsonOutputFormatter>()
                .BindingDetails(binding => binding.BindingSource = BindingSource.Services);

            var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
            var parameter = new ParameterDescriptor
            {
                Name = "parameter-name",
                BindingInfo = bindingInfo,
                ParameterType = typeof(JsonOutputFormatter),
            };

            var testContext = ModelBindingTestHelper.GetTestContext();
            testContext.MetadataProvider = metadataProvider;
            var modelState = testContext.ModelState;

            // Act
            var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);

            // Assert
            Assert.True(modelBindingResult.IsModelSet);
            Assert.IsType<JsonOutputFormatter>(modelBindingResult.Model);

            Assert.True(modelState.IsValid);
            Assert.Empty(modelState);
        }
        public async Task BinderTypeOnProperty_WithData_EmptyPrefix_GetsBound(BindingInfo bindingInfo)
        {
            // Arrange
            var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
            var parameter = new ParameterDescriptor
            {
                Name = "Parameter1",
                BindingInfo = bindingInfo,
                ParameterType = typeof(Person3),
            };

            var testContext = ModelBindingTestHelper.GetTestContext();
            var modelState = testContext.ModelState;

            // Act
            var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);

            // Assert
            // ModelBindingResult
            Assert.True(modelBindingResult.IsModelSet);

            // Model
            var person = Assert.IsType<Person3>(modelBindingResult.Model);
            Assert.NotNull(person.Address);
            Assert.Equal("SomeStreet", person.Address.Street);

            // ModelState
            Assert.True(modelState.IsValid);
            var kvp = Assert.Single(modelState);
            Assert.Equal("Address.Street", kvp.Key);
            var entry = kvp.Value;
            Assert.NotNull(entry);
            Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
            Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
        }
Beispiel #15
0
        public void CreateBinder_PassesExpectedBindingInfo(
            BindingInfo parameterBindingInfo,
            BindingMetadata bindingMetadata,
            BindingInfo expectedInfo)
        {
            // Arrange
            var metadataProvider = new TestModelMetadataProvider();
            metadataProvider.ForType<Employee>().BindingDetails(binding =>
            {
                binding.BinderModelName = bindingMetadata.BinderModelName;
                binding.BinderType = bindingMetadata.BinderType;
                binding.BindingSource = bindingMetadata.BindingSource;
                if (bindingMetadata.PropertyFilterProvider != null)
                {
                    binding.PropertyFilterProvider = bindingMetadata.PropertyFilterProvider;
                }
            });

            var modelBinder = Mock.Of<IModelBinder>();
            var modelBinderProvider = new TestModelBinderProvider(context =>
            {
                Assert.Equal(typeof(Employee), context.Metadata.ModelType);

                Assert.NotNull(context.BindingInfo);
                Assert.Equal(expectedInfo.BinderModelName, context.BindingInfo.BinderModelName, StringComparer.Ordinal);
                Assert.Equal(expectedInfo.BinderType, context.BindingInfo.BinderType);
                Assert.Equal(expectedInfo.BindingSource, context.BindingInfo.BindingSource);
                Assert.Same(expectedInfo.PropertyFilterProvider, context.BindingInfo.PropertyFilterProvider);

                return modelBinder;
            });

            var options = new TestOptionsManager<MvcOptions>();
            options.Value.ModelBinderProviders.Insert(0, modelBinderProvider);

            var factory = new ModelBinderFactory(metadataProvider, options);
            var factoryContext = new ModelBinderFactoryContext
            {
                BindingInfo = parameterBindingInfo,
                Metadata = metadataProvider.GetMetadataForType(typeof(Employee)),
            };

            // Act & Assert
            var result = factory.CreateBinder(factoryContext);

            // Confirm our IModelBinderProvider was called.
            Assert.Same(modelBinder, result);
        }
        public async Task FromBodyOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
        {
            // Arrange
            var inputText = "{ \"Street\" : \"someStreet\" }";
            var metadataProvider = new TestModelMetadataProvider();
            metadataProvider
                .ForType<Address6>()
                .BindingDetails(binding => binding.BindingSource = BindingSource.Body);

            var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
            var parameter = new ParameterDescriptor
            {
                Name = "parameter-name",
                BindingInfo = bindingInfo,
                ParameterType = typeof(Address6),
            };

            var testContext = ModelBindingTestHelper.GetTestContext(
                request =>
                {
                    request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
                    request.ContentType = "application/json";
                });
            testContext.MetadataProvider = metadataProvider;
            var modelState = testContext.ModelState;

            // Act
            var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);

            // Assert
            Assert.True(modelBindingResult.IsModelSet);
            var address = Assert.IsType<Address6>(modelBindingResult.Model);
            Assert.Equal("someStreet", address.Street, StringComparer.Ordinal);

            Assert.True(modelState.IsValid);
            Assert.Empty(modelState);
        }
Beispiel #17
0
 /// <summary>
 /// Creates an <see cref="IModelBinder"/> for the given <paramref name="metadata"/>
 /// and <paramref name="bindingInfo"/>.
 /// </summary>
 /// <param name="metadata">The <see cref="ModelMetadata"/> for the model.</param>
 /// <param name="bindingInfo">The <see cref="BindingInfo"/> that should be used
 /// for creating the binder.</param>
 /// <returns>An <see cref="IModelBinder"/>.</returns>
 public virtual IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo)
 {
     throw new NotSupportedException();
 }
Beispiel #18
0
        /// <summary>
        /// Creates a new <see cref="DefaultModelBindingContext"/> for top-level model binding operation.
        /// </summary>
        /// <param name="actionContext">
        /// The <see cref="ActionContext"/> associated with the binding operation.
        /// </param>
        /// <param name="valueProvider">The <see cref="IValueProvider"/> to use for binding.</param>
        /// <param name="metadata"><see cref="ModelMetadata"/> associated with the model.</param>
        /// <param name="bindingInfo"><see cref="BindingInfo"/> associated with the model.</param>
        /// <param name="modelName">The name of the property or parameter being bound.</param>
        /// <returns>A new instance of <see cref="DefaultModelBindingContext"/>.</returns>
        public static ModelBindingContext CreateBindingContext(
            ActionContext actionContext,
            IValueProvider valueProvider,
            ModelMetadata metadata,
            BindingInfo bindingInfo,
            string modelName)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException(nameof(actionContext));
            }

            if (valueProvider == null)
            {
                throw new ArgumentNullException(nameof(valueProvider));
            }

            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            if (modelName == null)
            {
                throw new ArgumentNullException(nameof(modelName));
            }

            var binderModelName        = bindingInfo?.BinderModelName ?? metadata.BinderModelName;
            var bindingSource          = bindingInfo?.BindingSource ?? metadata.BindingSource;
            var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider;

            var bindingContext = new DefaultModelBindingContext()
            {
                ActionContext   = actionContext,
                BinderModelName = binderModelName,
                BindingSource   = bindingSource,
                PropertyFilter  = propertyFilterProvider?.PropertyFilter,
                ValidationState = new ValidationStateDictionary(),

                // Because this is the top-level context, FieldName and ModelName should be the same.
                FieldName         = binderModelName ?? modelName,
                ModelName         = binderModelName ?? modelName,
                OriginalModelName = binderModelName ?? modelName,

                IsTopLevelObject = true,
                ModelMetadata    = metadata,
                ModelState       = actionContext.ModelState,

                OriginalValueProvider = valueProvider,
                ValueProvider         = FilterValueProvider(valueProvider, bindingSource),
            };

            // mvcOptions may be null when this method is called in test scenarios.
            var mvcOptions = actionContext.HttpContext.RequestServices?.GetService <IOptions <MvcOptions> >();

            if (mvcOptions != null)
            {
                bindingContext.MaxModelBindingRecursionDepth = mvcOptions.Value.MaxModelBindingRecursionDepth;
            }

            return(bindingContext);
        }
        public async Task ModelNameOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo)
        {
            // Arrange
            var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
            var parameter = new ParameterDescriptor
            {
                Name = "parameter-name",
                BindingInfo = bindingInfo,
                ParameterType = typeof(Person12),
            };

            var testContext = ModelBindingTestHelper.GetTestContext(
                request => request.QueryString = new QueryString("?HomeAddress.Street=someStreet"));
            var modelState = testContext.ModelState;

            // Act
            var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);

            // Assert
            Assert.True(modelBindingResult.IsModelSet);
            var person = Assert.IsType<Person12>(modelBindingResult.Model);
            Assert.NotNull(person.Address);
            Assert.Equal("someStreet", person.Address.Street, StringComparer.Ordinal);

            Assert.True(modelState.IsValid);
            var kvp = Assert.Single(modelState);
            Assert.Equal("HomeAddress.Street", kvp.Key);
            var entry = kvp.Value;
            Assert.NotNull(entry);
            Assert.Empty(entry.Errors);
            Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
        }
        public async Task BindAttributeOnParameterType_WithData_Succeeds(BindingInfo bindingInfo)
        {
            // Arrange
            var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
            var parameter = new ParameterDescriptor
            {
                Name = "parameter-name",
                BindingInfo = bindingInfo,
                ParameterType = typeof(Address13),
            };

            var testContext = ModelBindingTestHelper.GetTestContext(
                request => request.QueryString = new QueryString("?Number=23&Street=someStreet&City=Redmond&State=WA"));
            var modelState = testContext.ModelState;

            // Act
            var modelBindingResult = await argumentBinder.BindModelAsync(parameter, testContext);

            // Assert
            Assert.True(modelBindingResult.IsModelSet);
            var address = Assert.IsType<Address13>(modelBindingResult.Model);
            Assert.Null(address.City);
            Assert.Equal(0, address.Number);
            Assert.Null(address.State);
            Assert.Equal("someStreet", address.Street, StringComparer.Ordinal);

            Assert.True(modelState.IsValid);
            var kvp = Assert.Single(modelState);
            Assert.Equal("Street", kvp.Key);
            var entry = kvp.Value;
            Assert.NotNull(entry);
            Assert.Empty(entry.Errors);
            Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
        }
Beispiel #21
0
        /// <summary>
        /// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>.
        /// <para>
        /// This overload does not account for <see cref="BindingInfo"/> specified via <see cref="ModelMetadata"/>. Consider using
        /// <see cref="GetBindingInfo(IEnumerable{object}, ModelMetadata)"/> overload, or <see cref="TryApplyBindingInfo(ModelMetadata)"/>
        /// on the result of this method to get a more accurate <see cref="BindingInfo"/> instance.
        /// </para>
        /// </summary>
        /// <param name="attributes">A collection of attributes which are used to construct <see cref="BindingInfo"/>
        /// </param>
        /// <returns>A new instance of <see cref="BindingInfo"/>.</returns>
        public static BindingInfo GetBindingInfo(IEnumerable <object> attributes)
        {
            var bindingInfo          = new BindingInfo();
            var isBindingInfoPresent = false;

            // BinderModelName
            foreach (var binderModelNameAttribute in attributes.OfType <IModelNameProvider>())
            {
                isBindingInfoPresent = true;
                if (binderModelNameAttribute?.Name != null)
                {
                    bindingInfo.BinderModelName = binderModelNameAttribute.Name;
                    break;
                }
            }

            // BinderType
            foreach (var binderTypeAttribute in attributes.OfType <IBinderTypeProviderMetadata>())
            {
                isBindingInfoPresent = true;
                if (binderTypeAttribute.BinderType != null)
                {
                    bindingInfo.BinderType = binderTypeAttribute.BinderType;
                    break;
                }
            }

            // BindingSource
            foreach (var bindingSourceAttribute in attributes.OfType <IBindingSourceMetadata>())
            {
                isBindingInfoPresent = true;
                if (bindingSourceAttribute.BindingSource != null)
                {
                    bindingInfo.BindingSource = bindingSourceAttribute.BindingSource;
                    break;
                }
            }

            // PropertyFilterProvider
            var propertyFilterProviders = attributes.OfType <IPropertyFilterProvider>().ToArray();

            if (propertyFilterProviders.Length == 1)
            {
                isBindingInfoPresent = true;
                bindingInfo.PropertyFilterProvider = propertyFilterProviders[0];
            }
            else if (propertyFilterProviders.Length > 1)
            {
                isBindingInfoPresent = true;
                bindingInfo.PropertyFilterProvider = new CompositePropertyFilterProvider(propertyFilterProviders);
            }

            // RequestPredicate
            foreach (var requestPredicateProvider in attributes.OfType <IRequestPredicateProvider>())
            {
                isBindingInfoPresent = true;
                if (requestPredicateProvider.RequestPredicate != null)
                {
                    bindingInfo.RequestPredicate = requestPredicateProvider.RequestPredicate;
                    break;
                }
            }

            return(isBindingInfoPresent ? bindingInfo : null);
        }