1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . Collections ;
6
+ using System . ComponentModel ;
7
+ using System . Globalization ;
8
+ using System . Reflection ;
9
+ using System . Runtime . ExceptionServices ;
10
+
11
+ namespace Mvc . JQuery . DataTables
12
+ {
13
+ internal static class ModelBindingHelper
14
+ {
15
+ /// <summary>
16
+ /// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <typeparamref name="T"/>.
17
+ /// </summary>
18
+ /// <typeparam name="T">The <see cref="Type"/> for conversion.</typeparam>
19
+ /// <param name="value">The value to convert."/></param>
20
+ /// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
21
+ /// <returns>
22
+ /// The converted value or the default value of <typeparamref name="T"/> if the value could not be converted.
23
+ /// </returns>
24
+ public static T ConvertTo < T > ( object value , CultureInfo culture )
25
+ {
26
+ var converted = ConvertTo ( value , typeof ( T ) , culture ) ;
27
+ return converted == null ? default ( T ) : ( T ) converted ;
28
+ }
29
+
30
+ /// <summary>
31
+ /// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <paramref name="type"/>.
32
+ /// </summary>
33
+ /// <param name="value">The value to convert."/></param>
34
+ /// <param name="type">The <see cref="Type"/> for conversion.</param>
35
+ /// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
36
+ /// <returns>
37
+ /// The converted value or <c>null</c> if the value could not be converted.
38
+ /// </returns>
39
+ public static object ConvertTo ( object value , Type type , CultureInfo culture )
40
+ {
41
+ if ( type == null )
42
+ {
43
+ throw new ArgumentNullException ( nameof ( type ) ) ;
44
+ }
45
+
46
+ if ( value == null )
47
+ {
48
+ // For value types, treat null values as though they were the default value for the type.
49
+ return type . GetTypeInfo ( ) . IsValueType ? Activator . CreateInstance ( type ) : null ;
50
+ }
51
+
52
+ if ( type . IsAssignableFrom ( value . GetType ( ) ) )
53
+ {
54
+ return value ;
55
+ }
56
+
57
+ var cultureToUse = culture ?? CultureInfo . InvariantCulture ;
58
+ return UnwrapPossibleArrayType ( value , type , cultureToUse ) ;
59
+ }
60
+
61
+ private static object UnwrapPossibleArrayType ( object value , Type destinationType , CultureInfo culture )
62
+ {
63
+ // array conversion results in four cases, as below
64
+ var valueAsArray = value as Array ;
65
+ if ( destinationType . IsArray )
66
+ {
67
+ var destinationElementType = destinationType . GetElementType ( ) ;
68
+ if ( valueAsArray != null )
69
+ {
70
+ // case 1: both destination + source type are arrays, so convert each element
71
+ var converted = ( IList ) Array . CreateInstance ( destinationElementType , valueAsArray . Length ) ;
72
+ for ( var i = 0 ; i < valueAsArray . Length ; i ++ )
73
+ {
74
+ converted [ i ] = ConvertSimpleType ( valueAsArray . GetValue ( i ) , destinationElementType , culture ) ;
75
+ }
76
+ return converted ;
77
+ }
78
+ else
79
+ {
80
+ // case 2: destination type is array but source is single element, so wrap element in
81
+ // array + convert
82
+ var element = ConvertSimpleType ( value , destinationElementType , culture ) ;
83
+ var converted = ( IList ) Array . CreateInstance ( destinationElementType , 1 ) ;
84
+ converted [ 0 ] = element ;
85
+ return converted ;
86
+ }
87
+ }
88
+ else if ( valueAsArray != null )
89
+ {
90
+ // case 3: destination type is single element but source is array, so extract first element + convert
91
+ if ( valueAsArray . Length > 0 )
92
+ {
93
+ value = valueAsArray . GetValue ( 0 ) ;
94
+ return ConvertSimpleType ( value , destinationType , culture ) ;
95
+ }
96
+ else
97
+ {
98
+ // case 3(a): source is empty array, so can't perform conversion
99
+ return null ;
100
+ }
101
+ }
102
+
103
+ // case 4: both destination + source type are single elements, so convert
104
+ return ConvertSimpleType ( value , destinationType , culture ) ;
105
+ }
106
+
107
+ private static object ConvertSimpleType ( object value , Type destinationType , CultureInfo culture )
108
+ {
109
+ if ( value == null || destinationType . IsAssignableFrom ( value . GetType ( ) ) )
110
+ {
111
+ return value ;
112
+ }
113
+
114
+ // In case of a Nullable object, we try again with its underlying type.
115
+ destinationType = UnwrapNullableType ( destinationType ) ;
116
+
117
+ // if this is a user-input value but the user didn't type anything, return no value
118
+ if ( value is string valueAsString && string . IsNullOrWhiteSpace ( valueAsString ) )
119
+ {
120
+ return null ;
121
+ }
122
+
123
+ var converter = TypeDescriptor . GetConverter ( destinationType ) ;
124
+ var canConvertFrom = converter . CanConvertFrom ( value . GetType ( ) ) ;
125
+ if ( ! canConvertFrom )
126
+ {
127
+ converter = TypeDescriptor . GetConverter ( value . GetType ( ) ) ;
128
+ }
129
+ if ( ! ( canConvertFrom || converter . CanConvertTo ( destinationType ) ) )
130
+ {
131
+ // EnumConverter cannot convert integer, so we verify manually
132
+ if ( destinationType . GetTypeInfo ( ) . IsEnum &&
133
+ ( value is int ||
134
+ value is uint ||
135
+ value is long ||
136
+ value is ulong ||
137
+ value is short ||
138
+ value is ushort ||
139
+ value is byte ||
140
+ value is sbyte ) )
141
+ {
142
+ return Enum . ToObject ( destinationType , value ) ;
143
+ }
144
+
145
+ throw new InvalidOperationException ( $ "NoConverterExists: { value . GetType ( ) } -> { destinationType } ") ;
146
+ }
147
+
148
+ try
149
+ {
150
+ return canConvertFrom
151
+ ? converter . ConvertFrom ( null , culture , value )
152
+ : converter . ConvertTo ( null , culture , value , destinationType ) ;
153
+ }
154
+ catch ( FormatException )
155
+ {
156
+ throw ;
157
+ }
158
+ catch ( Exception ex )
159
+ {
160
+ if ( ex . InnerException == null )
161
+ {
162
+ throw ;
163
+ }
164
+ else
165
+ {
166
+ // TypeConverter throws System.Exception wrapping the FormatException,
167
+ // so we throw the inner exception.
168
+ ExceptionDispatchInfo . Capture ( ex . InnerException ) . Throw ( ) ;
169
+
170
+ // This code is never reached because the previous line will always throw.
171
+ throw ;
172
+ }
173
+ }
174
+ }
175
+
176
+ private static Type UnwrapNullableType ( Type destinationType )
177
+ {
178
+ return Nullable . GetUnderlyingType ( destinationType ) ?? destinationType ;
179
+ }
180
+ }
181
+ }
0 commit comments