// 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.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; 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(ModelBindingContext bindingContext, IEnumerable incomingElements, Func> creator) { ICollection collection = bindingContext.Model as ICollection; if (collection == null || collection.IsReadOnly) { collection = creator(); bindingContext.Model = collection; } collection.Clear(); foreach (TElement element in incomingElements) { collection.Add(element); } } internal static void CreateOrReplaceDictionary(ModelBindingContext bindingContext, IEnumerable> incomingElements, Func> creator) { IDictionary dictionary = bindingContext.Model as IDictionary; 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; } } } /// /// Instantiate a generic binder. /// /// Type that is updatable by this binder. /// Type that will be created by the binder if necessary. /// Model binder type. /// Model type. /// // Example: GetGenericBinder(typeof(IList<>), typeof(List<>), typeof(ListBinder<>), ...) means that the ListBinder // type can update models that implement IList, and if for some reason the existing model instance is not // updatable the binder will create a List object and bind to that instead. This method will return a ListBinder // or null, depending on whether the type and updatability checks succeed. internal static IModelBinder GetGenericBinder(Type supportedInterfaceType, Type newInstanceType, Type openBinderType, Type modelType) { Contract.Assert(supportedInterfaceType != null); Contract.Assert(openBinderType != null); Contract.Assert(modelType != null); Type[] modelTypeArguments = GetGenericBinderTypeArgs(supportedInterfaceType, modelType); if (modelTypeArguments == null) { return null; } Type closedNewInstanceType = newInstanceType.MakeGenericType(modelTypeArguments); if (!modelType.IsAssignableFrom(closedNewInstanceType)) { return null; } Type closedBinderType = openBinderType.MakeGenericType(modelTypeArguments); var binder = (IModelBinder)Activator.CreateInstance(closedBinderType); return binder; } // Get the generic arguments for the binder, based on the model type. Or null if not compatible. internal static Type[] GetGenericBinderTypeArgs(Type supportedInterfaceType, Type modelType) { if (!modelType.IsGenericType || modelType.IsGenericTypeDefinition) { // not a closed generic type return null; } Type[] modelTypeArguments = modelType.GetGenericArguments(); if (modelTypeArguments.Length != supportedInterfaceType.GetGenericArguments().Length) { // wrong number of generic type arguments return null; } return modelTypeArguments; } [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 GetIndexNamesFromValueProviderResult(ValueProviderResult valueProviderResultIndex) { IEnumerable 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 GetZeroBasedIndexes() { int i = 0; while (true) { yield return i.ToString(CultureInfo.InvariantCulture); i++; } } } }