// Copyright © 2016 The CefSharp Authors. All rights reserved. // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; namespace CefSharp.ModelBinding { /// /// Default binder - used as a fallback when a specific modelbinder /// is not available. /// public class DefaultBinder : IBinder { private static readonly MethodInfo ToArrayMethodInfo = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Static); /// /// Bind to the given model type /// /// object to be converted into a model /// the target param type /// Bound model public virtual object Bind(object obj, Type targetType) { if (obj == null) { if (targetType.IsValueType) { //For value types (int, double, etc) we cannot return null, //we need to return the default value for that type. return Activator.CreateInstance(targetType); } return null; } var objType = obj.GetType(); // If the object can be directly assigned to the modelType then return immediately. if (targetType.IsAssignableFrom(objType)) { return obj; } if (targetType.IsEnum && targetType.IsEnumDefined(obj)) { return Enum.ToObject(targetType, obj); } var typeConverter = TypeDescriptor.GetConverter(objType); // If the object can be converted to the modelType (eg: double to int) if (typeConverter.CanConvertTo(targetType)) { return typeConverter.ConvertTo(obj, targetType); } if (targetType.IsCollection() || targetType.IsArray() || targetType.IsEnumerable()) { return BindCollection(targetType, objType, obj); } return BindObject(targetType, objType, obj); } /// /// Bind collection. /// /// the target param type. /// Type of the object. /// object to be converted into a model. /// /// An object. /// protected virtual object BindCollection(Type targetType, Type objType, object obj) { var collection = obj as ICollection; if (collection == null) { return null; } Type genericType = null; // Make sure it has a generic type if (targetType.GetTypeInfo().IsGenericType) { genericType = targetType.GetGenericArguments().FirstOrDefault(); } else { var ienumerable = targetType.GetInterfaces().Where(i => i.GetTypeInfo().IsGenericType).FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); genericType = ienumerable == null ? null : ienumerable.GetGenericArguments().FirstOrDefault(); } if (genericType == null) { // If we don't have a generic type then just use object genericType = typeof(object); } var modelType = typeof(List<>).MakeGenericType(genericType); var model = (IList)Activator.CreateInstance(modelType); var list = (IList)obj; for (var i = 0; i < collection.Count; i++) { var val = list.ElementAtOrDefault(i); //Previously we only called bind for IDictionary // and IList, we now bind for all values to allow for // type conversion like int -> double where javascript gives // us a mixed array of types, some int, some double (Issue #3129) var result = Bind(val, genericType); model.Add(result); } if (targetType.IsArray()) { var genericToArrayMethod = ToArrayMethodInfo.MakeGenericMethod(new[] { genericType }); return genericToArrayMethod.Invoke(null, new[] { model }); } return model; } /// /// Bind object. /// /// the target param type. /// Type of the object. /// object to be converted into a model. /// /// An object. /// protected virtual object BindObject(Type targetType, Type objType, object obj) { var model = Activator.CreateInstance(targetType, true); // If the object type is a dictionary (we're using ExpandoObject instead of Dictionary now) // Then attempt to bind all the members if (typeof(IDictionary).IsAssignableFrom(objType)) { var dictionary = (IDictionary)obj; var members = BindingMemberInfo.Collect(targetType).ToList(); foreach (var modelProperty in members) { object val; if (dictionary.TryGetValue(modelProperty.Name, out val)) { var propertyVal = Bind(val, modelProperty.Type); modelProperty.SetValue(model, propertyVal); } } } return model; } } }