// 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);
}
}
}