/src/System.Web.Http/Internal/CollectionModelBinderUtil.cs
https://bitbucket.org/mdavid/aspnetwebstack · C# · 145 lines · 106 code · 15 blank · 24 comment · 23 complexity · d1876a9007097d985ce30694276aade7 MD5 · raw file
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Globalization;
- using System.Web.Http.Metadata;
- using System.Web.Http.ModelBinding;
- using System.Web.Http.ValueProviders;
- namespace System.Web.Http.Internal
- {
- internal static class CollectionModelBinderUtil
- {
- internal static void CreateOrReplaceCollection<TElement>(ModelBindingContext bindingContext, IEnumerable<TElement> incomingElements, Func<ICollection<TElement>> creator)
- {
- ICollection<TElement> collection = bindingContext.Model as ICollection<TElement>;
- if (collection == null || collection.IsReadOnly)
- {
- collection = creator();
- bindingContext.Model = collection;
- }
- collection.Clear();
- foreach (TElement element in incomingElements)
- {
- collection.Add(element);
- }
- }
- internal static void CreateOrReplaceDictionary<TKey, TValue>(ModelBindingContext bindingContext, IEnumerable<KeyValuePair<TKey, TValue>> incomingElements, Func<IDictionary<TKey, TValue>> creator)
- {
- IDictionary<TKey, TValue> dictionary = bindingContext.Model as IDictionary<TKey, TValue>;
- if (dictionary == null || dictionary.IsReadOnly)
- {
- dictionary = creator();
- bindingContext.Model = dictionary;
- }
- dictionary.Clear();
- foreach (var element in incomingElements)
- {
- if (element.Key != null)
- {
- dictionary[element.Key] = element.Value;
- }
- }
- }
- // supportedInterfaceType: type that is updatable by this binder
- // newInstanceType: type that will be created by the binder if necessary
- // openBinderType: model binder type
- // modelMetadata: metadata for the model to bind
- //
- // example: GetGenericBinder(typeof(IList<>), typeof(List<>), typeof(ListBinder<>), ...) means that the ListBinder<T>
- // type can update models that implement IList<T>, and if for some reason the existing model instance is not
- // updatable the binder will create a List<T> object and bind to that instead. This method will return a ListBinder<T>
- // or null, depending on whether the type and updatability checks succeed.
- internal static IModelBinder GetGenericBinder(Type supportedInterfaceType, Type newInstanceType, Type openBinderType, ModelMetadata modelMetadata)
- {
- Type[] typeArguments = GetTypeArgumentsForUpdatableGenericCollection(supportedInterfaceType, newInstanceType, modelMetadata);
- return (typeArguments != null) ? (IModelBinder)Activator.CreateInstance(openBinderType.MakeGenericType(typeArguments)) : null;
- }
- [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Http.ValueProviders.ValueProviderResult.ConvertTo(System.Type)", Justification = "The ValueProviderResult already has the necessary context to perform a culture-aware conversion.")]
- internal static IEnumerable<string> GetIndexNamesFromValueProviderResult(ValueProviderResult valueProviderResultIndex)
- {
- IEnumerable<string> indexNames = null;
- if (valueProviderResultIndex != null)
- {
- string[] indexes = (string[])valueProviderResultIndex.ConvertTo(typeof(string[]));
- if (indexes != null && indexes.Length > 0)
- {
- indexNames = indexes;
- }
- }
- return indexNames;
- }
- internal static IEnumerable<string> GetZeroBasedIndexes()
- {
- int i = 0;
- while (true)
- {
- yield return i.ToString(CultureInfo.InvariantCulture);
- i++;
- }
- }
- // Returns the generic type arguments for the model type if updatable, else null.
- // supportedInterfaceType: open type (like IList<>) of supported interface, must implement ICollection<>
- // newInstanceType: open type (like List<>) of object that will be created, must implement supportedInterfaceType
- internal static Type[] GetTypeArgumentsForUpdatableGenericCollection(Type supportedInterfaceType, Type newInstanceType, ModelMetadata modelMetadata)
- {
- /*
- * Check that we can extract proper type arguments from the model.
- */
- if (!modelMetadata.ModelType.IsGenericType || modelMetadata.ModelType.IsGenericTypeDefinition)
- {
- // not a closed generic type
- return null;
- }
- Type[] modelTypeArguments = modelMetadata.ModelType.GetGenericArguments();
- if (modelTypeArguments.Length != supportedInterfaceType.GetGenericArguments().Length)
- {
- // wrong number of generic type arguments
- return null;
- }
- /*
- * Is it possible just to change the reference rather than update the collection in-place?
- */
- if (!modelMetadata.IsReadOnly)
- {
- Type closedNewInstanceType = newInstanceType.MakeGenericType(modelTypeArguments);
- if (modelMetadata.ModelType.IsAssignableFrom(closedNewInstanceType))
- {
- return modelTypeArguments;
- }
- }
- /*
- * At this point, we know we can't change the reference, so we need to verify that
- * the model instance can be updated in-place.
- */
- Type closedSupportedInterfaceType = supportedInterfaceType.MakeGenericType(modelTypeArguments);
- if (!closedSupportedInterfaceType.IsInstanceOfType(modelMetadata.Model))
- {
- return null; // not instance of correct interface
- }
- Type closedCollectionType = TypeHelper.ExtractGenericInterface(closedSupportedInterfaceType, typeof(ICollection<>));
- bool collectionInstanceIsReadOnly = (bool)closedCollectionType.GetProperty("IsReadOnly").GetValue(modelMetadata.Model, null);
- if (collectionInstanceIsReadOnly)
- {
- return null;
- }
- else
- {
- return modelTypeArguments;
- }
- }
- }
- }