PageRenderTime 23ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. using System.Collections.Generic;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using System.Web.Http.Metadata;
  5. using System.Web.Http.ModelBinding;
  6. using System.Web.Http.ValueProviders;
  7. namespace System.Web.Http.Internal
  8. {
  9. internal static class CollectionModelBinderUtil
  10. {
  11. internal static void CreateOrReplaceCollection<TElement>(ModelBindingContext bindingContext, IEnumerable<TElement> incomingElements, Func<ICollection<TElement>> creator)
  12. {
  13. ICollection<TElement> collection = bindingContext.Model as ICollection<TElement>;
  14. if (collection == null || collection.IsReadOnly)
  15. {
  16. collection = creator();
  17. bindingContext.Model = collection;
  18. }
  19. collection.Clear();
  20. foreach (TElement element in incomingElements)
  21. {
  22. collection.Add(element);
  23. }
  24. }
  25. internal static void CreateOrReplaceDictionary<TKey, TValue>(ModelBindingContext bindingContext, IEnumerable<KeyValuePair<TKey, TValue>> incomingElements, Func<IDictionary<TKey, TValue>> creator)
  26. {
  27. IDictionary<TKey, TValue> dictionary = bindingContext.Model as IDictionary<TKey, TValue>;
  28. if (dictionary == null || dictionary.IsReadOnly)
  29. {
  30. dictionary = creator();
  31. bindingContext.Model = dictionary;
  32. }
  33. dictionary.Clear();
  34. foreach (var element in incomingElements)
  35. {
  36. if (element.Key != null)
  37. {
  38. dictionary[element.Key] = element.Value;
  39. }
  40. }
  41. }
  42. // supportedInterfaceType: type that is updatable by this binder
  43. // newInstanceType: type that will be created by the binder if necessary
  44. // openBinderType: model binder type
  45. // modelMetadata: metadata for the model to bind
  46. //
  47. // example: GetGenericBinder(typeof(IList<>), typeof(List<>), typeof(ListBinder<>), ...) means that the ListBinder<T>
  48. // type can update models that implement IList<T>, and if for some reason the existing model instance is not
  49. // updatable the binder will create a List<T> object and bind to that instead. This method will return a ListBinder<T>
  50. // or null, depending on whether the type and updatability checks succeed.
  51. internal static IModelBinder GetGenericBinder(Type supportedInterfaceType, Type newInstanceType, Type openBinderType, ModelMetadata modelMetadata)
  52. {
  53. Type[] typeArguments = GetTypeArgumentsForUpdatableGenericCollection(supportedInterfaceType, newInstanceType, modelMetadata);
  54. return (typeArguments != null) ? (IModelBinder)Activator.CreateInstance(openBinderType.MakeGenericType(typeArguments)) : null;
  55. }
  56. [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.")]
  57. internal static IEnumerable<string> GetIndexNamesFromValueProviderResult(ValueProviderResult valueProviderResultIndex)
  58. {
  59. IEnumerable<string> indexNames = null;
  60. if (valueProviderResultIndex != null)
  61. {
  62. string[] indexes = (string[])valueProviderResultIndex.ConvertTo(typeof(string[]));
  63. if (indexes != null && indexes.Length > 0)
  64. {
  65. indexNames = indexes;
  66. }
  67. }
  68. return indexNames;
  69. }
  70. internal static IEnumerable<string> GetZeroBasedIndexes()
  71. {
  72. int i = 0;
  73. while (true)
  74. {
  75. yield return i.ToString(CultureInfo.InvariantCulture);
  76. i++;
  77. }
  78. }
  79. // Returns the generic type arguments for the model type if updatable, else null.
  80. // supportedInterfaceType: open type (like IList<>) of supported interface, must implement ICollection<>
  81. // newInstanceType: open type (like List<>) of object that will be created, must implement supportedInterfaceType
  82. internal static Type[] GetTypeArgumentsForUpdatableGenericCollection(Type supportedInterfaceType, Type newInstanceType, ModelMetadata modelMetadata)
  83. {
  84. /*
  85. * Check that we can extract proper type arguments from the model.
  86. */
  87. if (!modelMetadata.ModelType.IsGenericType || modelMetadata.ModelType.IsGenericTypeDefinition)
  88. {
  89. // not a closed generic type
  90. return null;
  91. }
  92. Type[] modelTypeArguments = modelMetadata.ModelType.GetGenericArguments();
  93. if (modelTypeArguments.Length != supportedInterfaceType.GetGenericArguments().Length)
  94. {
  95. // wrong number of generic type arguments
  96. return null;
  97. }
  98. /*
  99. * Is it possible just to change the reference rather than update the collection in-place?
  100. */
  101. if (!modelMetadata.IsReadOnly)
  102. {
  103. Type closedNewInstanceType = newInstanceType.MakeGenericType(modelTypeArguments);
  104. if (modelMetadata.ModelType.IsAssignableFrom(closedNewInstanceType))
  105. {
  106. return modelTypeArguments;
  107. }
  108. }
  109. /*
  110. * At this point, we know we can't change the reference, so we need to verify that
  111. * the model instance can be updated in-place.
  112. */
  113. Type closedSupportedInterfaceType = supportedInterfaceType.MakeGenericType(modelTypeArguments);
  114. if (!closedSupportedInterfaceType.IsInstanceOfType(modelMetadata.Model))
  115. {
  116. return null; // not instance of correct interface
  117. }
  118. Type closedCollectionType = TypeHelper.ExtractGenericInterface(closedSupportedInterfaceType, typeof(ICollection<>));
  119. bool collectionInstanceIsReadOnly = (bool)closedCollectionType.GetProperty("IsReadOnly").GetValue(modelMetadata.Model, null);
  120. if (collectionInstanceIsReadOnly)
  121. {
  122. return null;
  123. }
  124. else
  125. {
  126. return modelTypeArguments;
  127. }
  128. }
  129. }
  130. }