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