况洋洋
2025-07-04 0d247bd2a17e0f99f3609774a1ce54ae00857997
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// 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;
using CefSharp.JavascriptBinding;
 
namespace CefSharp.ModelBinding
{
    /// <summary>
    /// Default binder - used as a fallback when a specific modelbinder
    /// is not available.
    /// </summary>
    public class DefaultBinder : IBinder
    {
        private static readonly MethodInfo ToArrayMethodInfo = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Static);
        private readonly IJavascriptNameConverter javascriptNameConverter;
 
        /// <summary>
        /// Static Instance of this binding that can be reused as it doesn't store any state information.
        /// Uses the <see cref="CamelCaseJavascriptNameConverter"/> naming converter
        /// </summary>
        public static readonly IBinder Instance = new DefaultBinder(new CamelCaseJavascriptNameConverter());
 
        /// <summary>
        /// Javascript Binder 
        /// </summary>
        /// <param name="javascriptNameConverter">name converter</param>
        public DefaultBinder(IJavascriptNameConverter javascriptNameConverter = null)
        {
            this.javascriptNameConverter = javascriptNameConverter;
        }
 
        /// <summary>
        /// Bind to the given model type
        /// </summary>
        /// <param name="obj">object to be converted into a model</param>
        /// <param name="targetType">the target param type</param>
        /// <returns>Bound model</returns>
        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);
        }
 
        /// <summary>
        /// Bind collection.
        /// </summary>
        /// <param name="targetType">the target param type.</param>
        /// <param name="objType">Type of the object.</param>
        /// <param name="obj">object to be converted into a model.</param>
        /// <returns>
        /// An object.
        /// </returns>
        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<object>)obj;
 
            for (var i = 0; i < collection.Count; i++)
            {
                var val = list.ElementAtOrDefault(i);
 
                //Previously we only called bind for IDictionary<string, object>
                // and IList<object>, 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;
        }
 
        /// <summary>
        /// Bind object.
        /// </summary>
        /// <param name="targetType">the target param type.</param>
        /// <param name="objType">Type of the object.</param>
        /// <param name="obj">object to be converted into a model.</param>
        /// <returns>
        /// An object.
        /// </returns>
        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<string, object>).IsAssignableFrom(objType))
            {
                var dictionary = (IDictionary<string, object>)obj;
                //TODO: Add Caching
                var members = BindingMemberInfo.Collect(targetType).ToList();
 
                //TODO: We currently go through all the propertie/fields and
                //atempt to find a match in the dictionary, we should probably
                //do the reverse and go through all the keys in the dictionary
                //and see if there is a match to a property member
                foreach (var modelProperty in members)
                {
                    object val;
 
                    var propertyName = GetPropertyName(modelProperty);
 
                    if (dictionary.TryGetValue(propertyName, out val))
                    {
                        var propertyVal = Bind(val, modelProperty.Type);
 
                        modelProperty.SetValue(model, propertyVal);
                    }
                }
            }
 
            return model;
        }
 
        private string GetPropertyName(BindingMemberInfo modelProperty)
        {
            if (javascriptNameConverter == null)
            {
                return modelProperty.Name;
            }
            return javascriptNameConverter.ConvertReturnedObjectPropertyAndFieldToNameJavascript(modelProperty);
        }
    }
}