1
/* ****************************************************************************
3 * Copyright (c) Microsoft Corporation. All rights reserved.
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.
9 * You must not remove this notice, or any other, from this software.
11 * ***************************************************************************/
13 namespace System
.Web
.Mvc
{
15 using System
.Collections
;
16 using System
.Collections
.Generic
;
17 using System
.ComponentModel
;
18 using System
.Diagnostics
.CodeAnalysis
;
19 using System
.Globalization
;
21 using System
.Reflection
;
22 using System
.Web
.Mvc
.Resources
;
24 public class DefaultModelBinder
: IModelBinder
{
26 private ModelBinderDictionary _binders
;
27 private static string _resourceClassKey
;
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
{
33 if (_binders
== null) {
34 _binders
= ModelBinders
.Binders
;
43 public static string ResourceClassKey
{
45 return _resourceClassKey
?? String
.Empty
;
48 _resourceClassKey
= value;
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
;
59 ModelBindingContext newBindingContext
= new ModelBindingContext() {
61 ModelName
= bindingContext
.ModelName
,
62 ModelState
= bindingContext
.ModelState
,
63 ModelType
= bindingContext
.ModelType
,
64 PropertyFilter
= newPropertyFilter
,
65 ValueProvider
= bindingContext
.ValueProvider
69 if (OnModelUpdating(controllerContext
, newBindingContext
)) {
70 BindProperties(controllerContext
, newBindingContext
);
71 OnModelUpdated(controllerContext
, newBindingContext
);
75 internal object BindComplexModel(ControllerContext controllerContext
, ModelBindingContext bindingContext
) {
76 object model
= bindingContext
.Model
;
77 Type modelType
= bindingContext
.ModelType
;
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
);
85 ModelBindingContext arrayBindingContext
= new ModelBindingContext() {
87 ModelName
= bindingContext
.ModelName
,
88 ModelState
= bindingContext
.ModelState
,
90 PropertyFilter
= bindingContext
.PropertyFilter
,
91 ValueProvider
= bindingContext
.ValueProvider
93 IList list
= (IList
)UpdateCollection(controllerContext
, arrayBindingContext
, elementType
);
99 Array array
= Array
.CreateInstance(elementType
, list
.Count
);
100 list
.CopyTo(array
, 0);
105 model
= CreateModel(controllerContext
,bindingContext
,modelType
);
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];
115 ModelBindingContext dictionaryBindingContext
= new ModelBindingContext() {
117 ModelName
= bindingContext
.ModelName
,
118 ModelState
= bindingContext
.ModelState
,
119 ModelType
= modelType
,
120 PropertyFilter
= bindingContext
.PropertyFilter
,
121 ValueProvider
= bindingContext
.ValueProvider
123 object dictionary
= UpdateDictionary(controllerContext
, dictionaryBindingContext
, keyType
, valueType
);
127 Type enumerableType
= ExtractGenericInterface(modelType
, typeof(IEnumerable
<>));
128 if (enumerableType
!= null) {
129 Type elementType
= enumerableType
.GetGenericArguments()[0];
131 Type collectionType
= typeof(ICollection
<>).MakeGenericType(elementType
);
132 if (collectionType
.IsInstanceOfType(model
)) {
133 ModelBindingContext collectionBindingContext
= new ModelBindingContext() {
135 ModelName
= bindingContext
.ModelName
,
136 ModelState
= bindingContext
.ModelState
,
137 ModelType
= modelType
,
138 PropertyFilter
= bindingContext
.PropertyFilter
,
139 ValueProvider
= bindingContext
.ValueProvider
141 object collection
= UpdateCollection(controllerContext
, collectionBindingContext
, elementType
);
146 // otherwise, just update the properties on the complex type
147 BindComplexElementalModel(controllerContext
, bindingContext
, model
);
151 public virtual object BindModel(ControllerContext controllerContext
, ModelBindingContext bindingContext
) {
152 if (bindingContext
== null) {
153 throw new ArgumentNullException("bindingContext");
156 bool performedFallback
= false;
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
169 performedFallback
= true;
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
);
186 if (TypeDescriptor
.GetConverter(bindingContext
.ModelType
).CanConvertFrom(typeof(string))) {
190 return BindComplexModel(controllerContext
, bindingContext
);
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
);
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
)) {
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
217 object newPropertyValue
= propertyBinder
.BindModel(controllerContext
, innerBindingContext
);
220 if (OnPropertyValidating(controllerContext
, bindingContext
, propertyDescriptor
, newPropertyValue
)) {
221 SetProperty(controllerContext
, bindingContext
, propertyDescriptor
, newPropertyValue
);
222 OnPropertyValidated(controllerContext
, bindingContext
, propertyDescriptor
, newPropertyValue
);
226 internal object BindSimpleModel(ControllerContext controllerContext
, ModelBindingContext bindingContext
, ValueProviderResult valueProviderResult
) {
227 bindingContext
.ModelState
.SetModelValue(bindingContext
.ModelName
, valueProviderResult
);
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
;
235 // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following
236 if (bindingContext
.ModelType
!= typeof(string)) {
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
);
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
);
255 Type collectionType
= typeof(ICollection
<>).MakeGenericType(elementType
);
256 if (collectionType
.IsInstanceOfType(modelCollection
)) {
257 CollectionHelpers
.ReplaceCollection(elementType
, modelCollection
, modelArray
);
259 return modelCollection
;
263 // case 3: user asked for an individual element
264 object model
= ConvertProviderResult(bindingContext
.ModelState
, bindingContext
.ModelName
, valueProviderResult
, bindingContext
.ModelType
);
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
) {
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
281 // special-case known common immutable types
282 if (type
== typeof(string)) {
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
) {
295 object convertedValue
= valueProviderResult
.ConvertTo(destinationType
);
296 return convertedValue
;
298 catch (Exception ex
) {
299 modelState
.AddModelError(modelStateKey
, ex
);
304 protected virtual object CreateModel(ControllerContext controllerContext
, ModelBindingContext bindingContext
, Type modelType
) {
305 Type typeToCreate
= modelType
;
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());
313 else if (genericTypeDefinition
== typeof(IEnumerable
<>) || genericTypeDefinition
== typeof(ICollection
<>) || genericTypeDefinition
== typeof(IList
<>)) {
314 typeToCreate
= typeof(List
<>).MakeGenericType(modelType
.GetGenericArguments());
318 // fallback to the type's default constructor
319 return Activator
.CreateInstance(typeToCreate
);
322 protected static string CreateSubIndexName(string prefix
, int index
) {
323 return String
.Format(CultureInfo
.InvariantCulture
, "{0}[{1}]", prefix
, index
);
326 protected static string CreateSubPropertyName(string prefix
, string propertyName
) {
327 return (!String
.IsNullOrEmpty(prefix
)) ? prefix
+ "." + propertyName
: propertyName
;
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
);
335 protected virtual PropertyDescriptorCollection
GetModelProperties(ControllerContext controllerContext
, ModelBindingContext bindingContext
) {
336 PropertyDescriptorCollection allProperties
= TypeDescriptor
.GetProperties(bindingContext
.ModelType
);
337 Predicate
<string> propertyFilter
= bindingContext
.PropertyFilter
;
339 var filteredProperties
= from PropertyDescriptor property
in allProperties
340 where
ShouldUpdateProperty(property
, propertyFilter
)
343 return new PropertyDescriptorCollection(filteredProperties
.ToArray());
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;
355 return resourceValue
?? MvcResources
.DefaultModelBinder_ValueRequired
;
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
);
368 protected virtual bool OnModelUpdating(ControllerContext controllerContext
, ModelBindingContext bindingContext
) {
369 // default implementation does nothing
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
);
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
388 string modelStateKey
= CreateSubPropertyName(bindingContext
.ModelName
, propertyDescriptor
.Name
);
389 return VerifyValueUsability(controllerContext
, bindingContext
.ModelState
, modelStateKey
, propertyDescriptor
.PropertyType
, value);
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
) {
400 propertyDescriptor
.SetValue(bindingContext
.Model
, value);
402 catch (Exception ex
) {
403 string modelStateKey
= CreateSubPropertyName(bindingContext
.ModelName
, propertyDescriptor
.Name
);
404 bindingContext
.ModelState
.AddModelError(modelStateKey
, ex
);
408 private static bool ShouldUpdateProperty(PropertyDescriptor property
, Predicate
<string> propertyFilter
) {
409 if (property
.IsReadOnly
&& !CanUpdateReadonlyTypedReference(property
.PropertyType
)) {
413 // if this property is rejected by the filter, move on
414 if (!propertyFilter(property
.Name
)) {
422 internal object UpdateCollection(ControllerContext controllerContext
, ModelBindingContext bindingContext
, Type elementType
) {
423 IModelBinder elementBinder
= Binders
.GetBinder(elementType
);
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
434 ModelBindingContext innerContext
= new ModelBindingContext() {
435 ModelName
= subIndexKey
,
436 ModelState
= bindingContext
.ModelState
,
437 ModelType
= elementType
,
438 PropertyFilter
= bindingContext
.PropertyFilter
,
439 ValueProvider
= bindingContext
.ValueProvider
441 object thisElement
= elementBinder
.BindModel(controllerContext
, innerContext
);
443 // we need to merge model errors up
444 VerifyValueUsability(controllerContext
, bindingContext
.ModelState
, subIndexKey
, elementType
, thisElement
);
445 modelList
.Add(thisElement
);
448 // if there weren't any elements at all in the request, just return
449 if (modelList
.Count
== 0) {
453 // replace the original collection
454 object collection
= bindingContext
.Model
;
455 CollectionHelpers
.ReplaceCollection(elementType
, collection
, modelList
);
459 internal object UpdateDictionary(ControllerContext controllerContext
, ModelBindingContext bindingContext
, Type keyType
, Type valueType
) {
460 IModelBinder keyBinder
= Binders
.GetBinder(keyType
);
461 IModelBinder valueBinder
= Binders
.GetBinder(valueType
);
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");
470 if (!(DictionaryHelpers
.DoesAnyKeyHavePrefix(bindingContext
.ValueProvider
, keyFieldKey
) && DictionaryHelpers
.DoesAnyKeyHavePrefix(bindingContext
.ValueProvider
, valueFieldKey
))) {
471 // we ran out of elements to pull
476 ModelBindingContext keyBindingContext
= new ModelBindingContext() {
477 ModelName
= keyFieldKey
,
478 ModelState
= bindingContext
.ModelState
,
480 ValueProvider
= bindingContext
.ValueProvider
482 object thisKey
= keyBinder
.BindModel(controllerContext
, keyBindingContext
);
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
492 ModelBindingContext valueBindingContext
= new ModelBindingContext() {
493 ModelName
= valueFieldKey
,
494 ModelState
= bindingContext
.ModelState
,
495 ModelType
= valueType
,
496 PropertyFilter
= bindingContext
.PropertyFilter
,
497 ValueProvider
= bindingContext
.ValueProvider
499 object thisValue
= valueBinder
.BindModel(controllerContext
, valueBindingContext
);
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
);
507 // if there weren't any elements at all in the request, just return
508 if (modelList
.Count
== 0) {
512 // replace the original collection
513 object dictionary
= bindingContext
.Model
;
514 CollectionHelpers
.ReplaceDictionary(keyType
, valueType
, dictionary
, modelList
);
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
);
525 // we don't care about "you must enter a value" messages if there was an error
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.
537 private static class CollectionHelpers
{
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
);
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 }
);
547 private static void ReplaceCollectionImpl
<T
>(ICollection
<T
> collection
, IEnumerable newContents
) {
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
);
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 }
);
564 private static void ReplaceDictionaryImpl
<TKey
, TValue
>(IDictionary
<TKey
, TValue
> dictionary
, IEnumerable
<KeyValuePair
<object, object>> newContents
) {
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
;