// 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.Diagnostics; using System.Linq; #if DNXCORE50 using System.Reflection; #endif using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding.Validation; namespace Microsoft.AspNet.Mvc.ModelBinding { /// /// implementation for binding dictionary values. /// /// Type of keys in the dictionary. /// Type of values in the dictionary. public class DictionaryModelBinder : CollectionModelBinder> { /// public override async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var result = await base.BindModelAsync(bindingContext); if (!result.IsModelSet) { // No match for the prefix at all. return result; } Debug.Assert(result.Model != null); var model = (IDictionary)result.Model; if (model.Count != 0) { // ICollection> approach was successful. return result; } var enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider; if (enumerableValueProvider == null) { // No IEnumerableValueProvider available for the fallback approach. For example the user may have // replaced the ValueProvider with something other than a CompositeValueProvider. return result; } // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first. var keys = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName); if (!keys.Any()) { // No entries with the expected keys. return result; } // Update the existing successful but empty ModelBindingResult. var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue)); var valueBindingContext = ModelBindingContext.CreateChildBindingContext( bindingContext, valueMetadata, fieldName: bindingContext.FieldName, modelName: bindingContext.ModelName, model: null); var modelBinder = bindingContext.OperationBindingContext.ModelBinder; var keyMappings = new Dictionary(StringComparer.Ordinal); foreach (var kvp in keys) { // Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use // that culture when rendering a form. var convertedKey = ModelBindingHelper.ConvertTo(kvp.Key, culture: null); valueBindingContext.ModelName = kvp.Value; var valueResult = await modelBinder.BindModelAsync(valueBindingContext); // Always add an entry to the dictionary but validate only if binding was successful. model[convertedKey] = ModelBindingHelper.CastOrDefault(valueResult.Model); keyMappings.Add(kvp.Key, convertedKey); } bindingContext.ValidationState.Add(model, new ValidationStateEntry() { Strategy = new ShortFormDictionaryValidationStrategy(keyMappings, valueMetadata), }); return result; } /// protected override object ConvertToCollectionType( Type targetType, IEnumerable> collection) { if (collection == null) { return null; } if (targetType.IsAssignableFrom(typeof(Dictionary))) { // Collection is a List>, never already a Dictionary. return collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } var newCollection = CreateInstance(targetType); CopyToModel(newCollection, collection); return newCollection; } /// protected override object CreateEmptyCollection(Type targetType) { if (targetType.IsAssignableFrom(typeof(Dictionary))) { // Simple case such as IDictionary. return new Dictionary(); } return CreateInstance(targetType); } } }