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