PageRenderTime 72ms CodeModel.GetById 41ms RepoModel.GetById 1ms app.codeStats 0ms

/mcs/class/System.Web.Mvc/System.Web.Mvc/DefaultModelBinder.cs

https://bitbucket.org/foobar22/mono
C# | 578 lines | 419 code | 87 blank | 72 comment | 83 complexity | 7a170e20498a52e36e8c2557eb5855b1 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Unlicense, Apache-2.0, LGPL-2.0
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation. All rights reserved.
  4. *
  5. * This software is subject to the Microsoft Public License (Ms-PL).
  6. * A copy of the license can be found in the license.htm file included
  7. * in this distribution.
  8. *
  9. * You must not remove this notice, or any other, from this software.
  10. *
  11. * ***************************************************************************/
  12. namespace System.Web.Mvc {
  13. using System;
  14. using System.Collections;
  15. using System.Collections.Generic;
  16. using System.ComponentModel;
  17. using System.Diagnostics.CodeAnalysis;
  18. using System.Globalization;
  19. using System.Linq;
  20. using System.Reflection;
  21. using System.Web.Mvc.Resources;
  22. public class DefaultModelBinder : IModelBinder {
  23. private ModelBinderDictionary _binders;
  24. private static string _resourceClassKey;
  25. [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
  26. Justification = "Property is settable so that the dictionary can be provided for unit testing purposes.")]
  27. protected internal ModelBinderDictionary Binders {
  28. get {
  29. if (_binders == null) {
  30. _binders = ModelBinders.Binders;
  31. }
  32. return _binders;
  33. }
  34. set {
  35. _binders = value;
  36. }
  37. }
  38. public static string ResourceClassKey {
  39. get {
  40. return _resourceClassKey ?? String.Empty;
  41. }
  42. set {
  43. _resourceClassKey = value;
  44. }
  45. }
  46. internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
  47. // need to replace the property filter + model object and create an inner binding context
  48. BindAttribute bindAttr = (BindAttribute)TypeDescriptor.GetAttributes(bindingContext.ModelType)[typeof(BindAttribute)];
  49. Predicate<string> newPropertyFilter = (bindAttr != null)
  50. ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
  51. : bindingContext.PropertyFilter;
  52. ModelBindingContext newBindingContext = new ModelBindingContext() {
  53. Model = model,
  54. ModelName = bindingContext.ModelName,
  55. ModelState = bindingContext.ModelState,
  56. ModelType = bindingContext.ModelType,
  57. PropertyFilter = newPropertyFilter,
  58. ValueProvider = bindingContext.ValueProvider
  59. };
  60. // validation
  61. if (OnModelUpdating(controllerContext, newBindingContext)) {
  62. BindProperties(controllerContext, newBindingContext);
  63. OnModelUpdated(controllerContext, newBindingContext);
  64. }
  65. }
  66. internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  67. object model = bindingContext.Model;
  68. Type modelType = bindingContext.ModelType;
  69. // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created
  70. if (model == null && modelType.IsArray) {
  71. Type elementType = modelType.GetElementType();
  72. Type listType = typeof(List<>).MakeGenericType(elementType);
  73. object collection = CreateModel(controllerContext, bindingContext, listType);
  74. ModelBindingContext arrayBindingContext = new ModelBindingContext() {
  75. Model = collection,
  76. ModelName = bindingContext.ModelName,
  77. ModelState = bindingContext.ModelState,
  78. ModelType = listType,
  79. PropertyFilter = bindingContext.PropertyFilter,
  80. ValueProvider = bindingContext.ValueProvider
  81. };
  82. IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType);
  83. if (list == null) {
  84. return null;
  85. }
  86. Array array = Array.CreateInstance(elementType, list.Count);
  87. list.CopyTo(array, 0);
  88. return array;
  89. }
  90. if (model == null) {
  91. model = CreateModel(controllerContext,bindingContext,modelType);
  92. }
  93. // special-case IDictionary<,> and ICollection<>
  94. Type dictionaryType = ExtractGenericInterface(modelType, typeof(IDictionary<,>));
  95. if (dictionaryType != null) {
  96. Type[] genericArguments = dictionaryType.GetGenericArguments();
  97. Type keyType = genericArguments[0];
  98. Type valueType = genericArguments[1];
  99. ModelBindingContext dictionaryBindingContext = new ModelBindingContext() {
  100. Model = model,
  101. ModelName = bindingContext.ModelName,
  102. ModelState = bindingContext.ModelState,
  103. ModelType = modelType,
  104. PropertyFilter = bindingContext.PropertyFilter,
  105. ValueProvider = bindingContext.ValueProvider
  106. };
  107. object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
  108. return dictionary;
  109. }
  110. Type enumerableType = ExtractGenericInterface(modelType, typeof(IEnumerable<>));
  111. if (enumerableType != null) {
  112. Type elementType = enumerableType.GetGenericArguments()[0];
  113. Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
  114. if (collectionType.IsInstanceOfType(model)) {
  115. ModelBindingContext collectionBindingContext = new ModelBindingContext() {
  116. Model = model,
  117. ModelName = bindingContext.ModelName,
  118. ModelState = bindingContext.ModelState,
  119. ModelType = modelType,
  120. PropertyFilter = bindingContext.PropertyFilter,
  121. ValueProvider = bindingContext.ValueProvider
  122. };
  123. object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
  124. return collection;
  125. }
  126. }
  127. // otherwise, just update the properties on the complex type
  128. BindComplexElementalModel(controllerContext, bindingContext, model);
  129. return model;
  130. }
  131. public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  132. if (bindingContext == null) {
  133. throw new ArgumentNullException("bindingContext");
  134. }
  135. bool performedFallback = false;
  136. if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName)) {
  137. // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
  138. // to the empty prefix.
  139. if (bindingContext.FallbackToEmptyPrefix) {
  140. bindingContext = new ModelBindingContext() {
  141. Model = bindingContext.Model,
  142. ModelState = bindingContext.ModelState,
  143. ModelType = bindingContext.ModelType,
  144. PropertyFilter = bindingContext.PropertyFilter,
  145. ValueProvider = bindingContext.ValueProvider
  146. };
  147. performedFallback = true;
  148. }
  149. else {
  150. return null;
  151. }
  152. }
  153. // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
  154. // or by seeing if a value in the request exactly matches the name of the model we're binding.
  155. // Complex type = everything else.
  156. if (!performedFallback) {
  157. ValueProviderResult vpResult;
  158. bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out vpResult);
  159. if (vpResult != null) {
  160. return BindSimpleModel(controllerContext, bindingContext, vpResult);
  161. }
  162. }
  163. if (TypeDescriptor.GetConverter(bindingContext.ModelType).CanConvertFrom(typeof(string))) {
  164. return null;
  165. }
  166. return BindComplexModel(controllerContext, bindingContext);
  167. }
  168. private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  169. PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
  170. foreach (PropertyDescriptor property in properties) {
  171. BindProperty(controllerContext, bindingContext, property);
  172. }
  173. }
  174. protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
  175. // need to skip properties that aren't part of the request, else we might hit a StackOverflowException
  176. string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  177. if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, fullPropertyKey)) {
  178. return;
  179. }
  180. // call into the property's model binder
  181. IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
  182. object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
  183. ModelBindingContext innerBindingContext = new ModelBindingContext() {
  184. Model = originalPropertyValue,
  185. ModelName = fullPropertyKey,
  186. ModelState = bindingContext.ModelState,
  187. ModelType = propertyDescriptor.PropertyType,
  188. ValueProvider = bindingContext.ValueProvider
  189. };
  190. object newPropertyValue = propertyBinder.BindModel(controllerContext, innerBindingContext);
  191. // validation
  192. if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
  193. SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
  194. OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
  195. }
  196. }
  197. internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) {
  198. bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
  199. // if the value provider returns an instance of the requested data type, we can just short-circuit
  200. // the evaluation and return that instance
  201. if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) {
  202. return valueProviderResult.RawValue;
  203. }
  204. // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following
  205. if (bindingContext.ModelType != typeof(string)) {
  206. // conversion results in 3 cases, as below
  207. if (bindingContext.ModelType.IsArray) {
  208. // case 1: user asked for an array
  209. // ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly
  210. object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
  211. return modelArray;
  212. }
  213. Type enumerableType = ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
  214. if (enumerableType != null) {
  215. // case 2: user asked for a collection rather than an array
  216. // need to call ConvertTo() on the array type, then copy the array to the collection
  217. object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
  218. Type elementType = enumerableType.GetGenericArguments()[0];
  219. Type arrayType = elementType.MakeArrayType();
  220. object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
  221. Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
  222. if (collectionType.IsInstanceOfType(modelCollection)) {
  223. CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
  224. }
  225. return modelCollection;
  226. }
  227. }
  228. // case 3: user asked for an individual element
  229. object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
  230. return model;
  231. }
  232. private static bool CanUpdateReadonlyTypedReference(Type type) {
  233. // value types aren't strictly immutable, but because they have copy-by-value semantics
  234. // we can't update a value type that is marked readonly
  235. if (type.IsValueType) {
  236. return false;
  237. }
  238. // arrays are mutable, but because we can't change their length we shouldn't try
  239. // to update an array that is referenced readonly
  240. if (type.IsArray) {
  241. return false;
  242. }
  243. // special-case known common immutable types
  244. if (type == typeof(string)) {
  245. return false;
  246. }
  247. return true;
  248. }
  249. [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Mvc.ValueProviderResult.ConvertTo(System.Type)",
  250. Justification = "The target object should make the correct culture determination, not this method.")]
  251. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
  252. Justification = "We're recording this exception so that we can act on it later.")]
  253. private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) {
  254. try {
  255. object convertedValue = valueProviderResult.ConvertTo(destinationType);
  256. return convertedValue;
  257. }
  258. catch (Exception ex) {
  259. modelState.AddModelError(modelStateKey, ex);
  260. return null;
  261. }
  262. }
  263. protected virtual object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
  264. Type typeToCreate = modelType;
  265. // we can understand some collection interfaces, e.g. IList<>, IDictionary<,>
  266. if (modelType.IsGenericType) {
  267. Type genericTypeDefinition = modelType.GetGenericTypeDefinition();
  268. if (genericTypeDefinition == typeof(IDictionary<,>)) {
  269. typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());
  270. }
  271. else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) {
  272. typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments());
  273. }
  274. }
  275. // fallback to the type's default constructor
  276. return Activator.CreateInstance(typeToCreate);
  277. }
  278. protected static string CreateSubIndexName(string prefix, int index) {
  279. return String.Format(CultureInfo.InvariantCulture, "{0}[{1}]", prefix, index);
  280. }
  281. protected static string CreateSubPropertyName(string prefix, string propertyName) {
  282. return (!String.IsNullOrEmpty(prefix)) ? prefix + "." + propertyName : propertyName;
  283. }
  284. private static Type ExtractGenericInterface(Type queryType, Type interfaceType) {
  285. Func<Type, bool> matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType;
  286. return (matchesInterface(queryType)) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface);
  287. }
  288. protected virtual PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  289. PropertyDescriptorCollection allProperties = TypeDescriptor.GetProperties(bindingContext.ModelType);
  290. Predicate<string> propertyFilter = bindingContext.PropertyFilter;
  291. var filteredProperties = from PropertyDescriptor property in allProperties
  292. where ShouldUpdateProperty(property, propertyFilter)
  293. select property;
  294. return new PropertyDescriptorCollection(filteredProperties.ToArray());
  295. }
  296. private static string GetValueRequiredResource(ControllerContext controllerContext) {
  297. string resourceValue = null;
  298. if (!String.IsNullOrEmpty(ResourceClassKey) && (controllerContext != null) && (controllerContext.HttpContext != null)) {
  299. // If the user specified a ResourceClassKey try to load the resource they specified.
  300. // If the class key is invalid, an exception will be thrown.
  301. // If the class key is valid but the resource is not found, it returns null, in which
  302. // case it will fall back to the MVC default error message.
  303. resourceValue = controllerContext.HttpContext.GetGlobalResourceObject(ResourceClassKey, "PropertyValueRequired", CultureInfo.CurrentUICulture) as string;
  304. }
  305. return resourceValue ?? MvcResources.DefaultModelBinder_ValueRequired;
  306. }
  307. protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  308. IDataErrorInfo errorProvider = bindingContext.Model as IDataErrorInfo;
  309. if (errorProvider != null) {
  310. string errorText = errorProvider.Error;
  311. if (!String.IsNullOrEmpty(errorText)) {
  312. bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorText);
  313. }
  314. }
  315. }
  316. protected virtual bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  317. // default implementation does nothing
  318. return true;
  319. }
  320. protected virtual void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {
  321. IDataErrorInfo errorProvider = bindingContext.Model as IDataErrorInfo;
  322. if (errorProvider != null) {
  323. string errorText = errorProvider[propertyDescriptor.Name];
  324. if (!String.IsNullOrEmpty(errorText)) {
  325. string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  326. bindingContext.ModelState.AddModelError(modelStateKey, errorText);
  327. }
  328. }
  329. }
  330. protected virtual bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {
  331. // default implementation just checks to make sure that required text entry fields aren't left blank
  332. string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  333. return VerifyValueUsability(controllerContext, bindingContext.ModelState, modelStateKey, propertyDescriptor.PropertyType, value);
  334. }
  335. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
  336. Justification = "We're recording this exception so that we can act on it later.")]
  337. protected virtual void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {
  338. if (propertyDescriptor.IsReadOnly) {
  339. return;
  340. }
  341. try {
  342. propertyDescriptor.SetValue(bindingContext.Model, value);
  343. }
  344. catch (Exception ex) {
  345. string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  346. bindingContext.ModelState.AddModelError(modelStateKey, ex);
  347. }
  348. }
  349. private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) {
  350. if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) {
  351. return false;
  352. }
  353. // if this property is rejected by the filter, move on
  354. if (!propertyFilter(property.Name)) {
  355. return false;
  356. }
  357. // otherwise, allow
  358. return true;
  359. }
  360. internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
  361. IModelBinder elementBinder = Binders.GetBinder(elementType);
  362. // build up a list of items from the request
  363. List<object> modelList = new List<object>();
  364. for (int currentIndex = 0; ; currentIndex++) {
  365. string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
  366. if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
  367. // we ran out of elements to pull
  368. break;
  369. }
  370. ModelBindingContext innerContext = new ModelBindingContext() {
  371. ModelName = subIndexKey,
  372. ModelState = bindingContext.ModelState,
  373. ModelType = elementType,
  374. PropertyFilter = bindingContext.PropertyFilter,
  375. ValueProvider = bindingContext.ValueProvider
  376. };
  377. object thisElement = elementBinder.BindModel(controllerContext, innerContext);
  378. // we need to merge model errors up
  379. VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
  380. modelList.Add(thisElement);
  381. }
  382. // if there weren't any elements at all in the request, just return
  383. if (modelList.Count == 0) {
  384. return null;
  385. }
  386. // replace the original collection
  387. object collection = bindingContext.Model;
  388. CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
  389. return collection;
  390. }
  391. internal object UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType) {
  392. IModelBinder keyBinder = Binders.GetBinder(keyType);
  393. IModelBinder valueBinder = Binders.GetBinder(valueType);
  394. // build up a list of items from the request
  395. List<KeyValuePair<object, object>> modelList = new List<KeyValuePair<object, object>>();
  396. for (int currentIndex = 0; ; currentIndex++) {
  397. string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
  398. string keyFieldKey = CreateSubPropertyName(subIndexKey, "key");
  399. string valueFieldKey = CreateSubPropertyName(subIndexKey, "value");
  400. if (!(DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, keyFieldKey) && DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, valueFieldKey))) {
  401. // we ran out of elements to pull
  402. break;
  403. }
  404. // bind the key
  405. ModelBindingContext keyBindingContext = new ModelBindingContext() {
  406. ModelName = keyFieldKey,
  407. ModelState = bindingContext.ModelState,
  408. ModelType = keyType,
  409. ValueProvider = bindingContext.ValueProvider
  410. };
  411. object thisKey = keyBinder.BindModel(controllerContext, keyBindingContext);
  412. // we need to merge model errors up
  413. VerifyValueUsability(controllerContext, bindingContext.ModelState, keyFieldKey, keyType, thisKey);
  414. if (!keyType.IsInstanceOfType(thisKey)) {
  415. // we can't add an invalid key, so just move on
  416. continue;
  417. }
  418. // bind the value
  419. ModelBindingContext valueBindingContext = new ModelBindingContext() {
  420. ModelName = valueFieldKey,
  421. ModelState = bindingContext.ModelState,
  422. ModelType = valueType,
  423. PropertyFilter = bindingContext.PropertyFilter,
  424. ValueProvider = bindingContext.ValueProvider
  425. };
  426. object thisValue = valueBinder.BindModel(controllerContext, valueBindingContext);
  427. // we need to merge model errors up
  428. VerifyValueUsability(controllerContext, bindingContext.ModelState, valueFieldKey, valueType, thisValue);
  429. KeyValuePair<object, object> kvp = new KeyValuePair<object, object>(thisKey, thisValue);
  430. modelList.Add(kvp);
  431. }
  432. // if there weren't any elements at all in the request, just return
  433. if (modelList.Count == 0) {
  434. return null;
  435. }
  436. // replace the original collection
  437. object dictionary = bindingContext.Model;
  438. CollectionHelpers.ReplaceDictionary(keyType, valueType, dictionary, modelList);
  439. return dictionary;
  440. }
  441. private static bool VerifyValueUsability(ControllerContext controllerContext, ModelStateDictionary modelState, string modelStateKey, Type elementType, object value) {
  442. if (value == null && !TypeHelpers.TypeAllowsNullValue(elementType)) {
  443. if (modelState.IsValidField(modelStateKey)) {
  444. // a required entry field was left blank
  445. string message = GetValueRequiredResource(controllerContext);
  446. modelState.AddModelError(modelStateKey, message);
  447. }
  448. // we don't care about "you must enter a value" messages if there was an error
  449. return false;
  450. }
  451. return true;
  452. }
  453. // This helper type is used because we're working with strongly-typed collections, but we don't know the Ts
  454. // ahead of time. By using the generic methods below, we can consolidate the collection-specific code in a
  455. // single helper type rather than having reflection-based calls spread throughout the DefaultModelBinder type.
  456. // There is a single point of entry to each of the methods below, so they're fairly simple to maintain.
  457. private static class CollectionHelpers {
  458. private static readonly MethodInfo _replaceCollectionMethod = typeof(CollectionHelpers).GetMethod("ReplaceCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic);
  459. private static readonly MethodInfo _replaceDictionaryMethod = typeof(CollectionHelpers).GetMethod("ReplaceDictionaryImpl", BindingFlags.Static | BindingFlags.NonPublic);
  460. public static void ReplaceCollection(Type collectionType, object collection, object newContents) {
  461. MethodInfo targetMethod = _replaceCollectionMethod.MakeGenericMethod(collectionType);
  462. targetMethod.Invoke(null, new object[] { collection, newContents });
  463. }
  464. private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents) {
  465. collection.Clear();
  466. if (newContents != null) {
  467. foreach (object item in newContents) {
  468. // if the item was not a T, some conversion failed. the error message will be propagated,
  469. // but in the meanwhile we need to make a placeholder element in the array.
  470. T castItem = (item is T) ? (T)item : default(T);
  471. collection.Add(castItem);
  472. }
  473. }
  474. }
  475. public static void ReplaceDictionary(Type keyType, Type valueType, object dictionary, object newContents) {
  476. MethodInfo targetMethod = _replaceDictionaryMethod.MakeGenericMethod(keyType, valueType);
  477. targetMethod.Invoke(null, new object[] { dictionary, newContents });
  478. }
  479. private static void ReplaceDictionaryImpl<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<object, object>> newContents) {
  480. dictionary.Clear();
  481. foreach (var item in newContents) {
  482. // if the item was not a T, some conversion failed. the error message will be propagated,
  483. // but in the meanwhile we need to make a placeholder element in the dictionary.
  484. TKey castKey = (TKey)item.Key; // this cast shouldn't fail
  485. TValue castValue = (item.Value is TValue) ? (TValue)item.Value : default(TValue);
  486. dictionary[castKey] = castValue;
  487. }
  488. }
  489. }
  490. }
  491. }