1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.Components
.Binder
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
20 using System
.ComponentModel
;
23 public class DefaultConverter
: MarshalByRefObject
, IConverter
25 public bool CanConvert(Type desiredType
, Type inputType
, object input
, out bool exactMatch
)
33 else if (inputType
== desiredType
)
37 else if (!desiredType
.IsInstanceOfType(input
))
39 TypeConverter conv
= TypeDescriptor
.GetConverter(desiredType
);
40 return (conv
!= null && conv
.CanConvertFrom(inputType
));
46 public object Convert(Type desiredType
, Type inputType
, object input
, out bool conversionSucceeded
)
48 if (inputType
== desiredType
) // Nothing to convert
50 if (inputType
== typeof(String
))
54 conversionSucceeded
= false;
57 else if (input
.ToString().Length
== 0)
59 conversionSucceeded
= true;
64 conversionSucceeded
= true;
65 return input
.ToString().Trim(' ');
70 conversionSucceeded
= true;
75 return Convert(desiredType
, input
, out conversionSucceeded
);
79 /// Convert the input param into the desired type
81 /// <param name="desiredType">Type of the desired</param>
82 /// <param name="input">The input</param>
83 /// <param name="conversionSucceeded">if <c>false</c> the return value must be ignored</param>
85 /// There are 3 possible cases when trying to convert:
86 /// 1) Input data for conversion missing (input is null or an empty String)
87 /// Returns default conversion value (based on desired type) and set <c>conversionSucceeded = false</c>
88 /// 2) Has input data but cannot convert to particular type
89 /// Throw exception and set <c>conversionSucceeded = false</c>
90 /// 3) Has input data and can convert to particular type
91 /// Return input converted to desired type and set <c>conversionSucceeded = true</c>
93 public object Convert(Type desiredType
, object input
, out bool conversionSucceeded
)
97 conversionSucceeded
= (input
!= null);
99 if (desiredType
.IsInstanceOfType(input
))
103 else if (desiredType
== typeof(String
))
105 if (conversionSucceeded
&& input
.GetType() == typeof(string) && ((String
) input
) == String
.Empty
)
110 return conversionSucceeded
? input
.ToString().Trim(' ') : null;
112 else if (desiredType
.IsArray
)
114 return conversionSucceeded
116 ConvertToArray(desiredType
, input
, ref conversionSucceeded
)
119 else if (desiredType
.IsEnum
)
121 return ConvertEnum(desiredType
, input
, ref conversionSucceeded
);
123 else if (desiredType
== typeof(Decimal
))
125 return ConvertDecimal(input
, ref conversionSucceeded
);
127 else if (desiredType
.IsPrimitive
)
129 return ConvertPrimitive(desiredType
, input
, ref conversionSucceeded
);
131 else if (desiredType
.IsGenericType
&&
132 desiredType
.GetGenericTypeDefinition() == typeof(Nullable
<>))
134 return ConvertNullable(desiredType
, input
, ref conversionSucceeded
);
136 else if (desiredType
== typeof(Guid
))
138 return ConvertGuid(input
, ref conversionSucceeded
);
140 else if (desiredType
== typeof(DateTime
))
142 return ConvertDate(input
, ref conversionSucceeded
);
144 else if (desiredType
== typeof(HttpPostedFile
))
148 else if (DataBinder
.IsGenericList(desiredType
))
150 return conversionSucceeded
151 ? ConvertGenericList(desiredType
, input
, ref conversionSucceeded
)
156 return ConvertUsingTypeConverter(desiredType
, input
, ref conversionSucceeded
);
159 catch(BindingException
)
163 catch(Exception inner
)
165 conversionSucceeded
= false;
167 ThrowInformativeException(desiredType
, input
, inner
);
172 private object ConvertNullable(Type desiredType
, object input
, ref bool conversionSucceeded
)
174 Type underlyingType
= Nullable
.GetUnderlyingType(desiredType
);
176 object value = Convert(underlyingType
, input
, out conversionSucceeded
);
178 if (conversionSucceeded
&& value != null)
180 Type typeToConstruct
= typeof(Nullable
<>).MakeGenericType(underlyingType
);
182 return Activator
.CreateInstance(typeToConstruct
, value);
184 else if (input
!= null)
186 conversionSucceeded
= true;
190 conversionSucceeded
= false;
196 private object ConvertGenericList(Type desiredType
, object input
, ref bool conversionSucceeded
)
198 Type elemType
= desiredType
.GetGenericArguments()[0];
200 input
= FixInputForMonoIfNeeded(elemType
, input
);
202 Type listType
= typeof(List
<>).MakeGenericType(elemType
);
203 IList result
= (IList
) Activator
.CreateInstance(listType
);
204 Array values
= input
as Array
;
206 bool elementConversionSucceeded
;
208 for(int i
= 0; i
< values
.Length
; i
++)
210 result
.Add(Convert(elemType
, values
.GetValue(i
), out elementConversionSucceeded
));
212 // if at least one list element get converted
213 // we consider the conversion a success
214 if (elementConversionSucceeded
&& !conversionSucceeded
)
216 conversionSucceeded
= true;
224 /// Fix for mod_mono issue where array values are passed as a comma seperated String.
226 /// <param name="elemType"></param>
227 /// <param name="input"></param>
228 /// <returns></returns>
229 private static object FixInputForMonoIfNeeded(Type elemType
, object input
)
231 if (!input
.GetType().IsArray
)
233 if (input
.GetType() == typeof(String
))
235 input
= NormalizeInput(input
);
237 if (input
.ToString() == String
.Empty
)
239 input
= Array
.CreateInstance(elemType
, 0);
243 input
= input
.ToString().Split(',');
248 throw new BindingException("Cannot convert to collection of {0} from type {1}", elemType
.FullName
,
249 input
.GetType().FullName
);
256 private object ConvertGuid(object input
, ref bool conversionSucceeded
)
258 conversionSucceeded
= true;
262 conversionSucceeded
= false;
266 String
value = NormalizeInput(input
);
268 if (value == String
.Empty
)
270 conversionSucceeded
= true;
275 return new Guid(value);
279 private object ConvertDecimal(object input
, ref bool conversionSucceeded
)
281 conversionSucceeded
= true;
285 conversionSucceeded
= false;
289 String
value = NormalizeInput(input
);
291 if (value == String
.Empty
)
293 conversionSucceeded
= true;
298 return System
.Convert
.ToDecimal(value);
302 private object ConvertPrimitive(Type desiredType
, object input
, ref bool conversionSucceeded
)
304 conversionSucceeded
= true;
306 String
value = NormalizeInput(input
);
308 if (IsBool(desiredType
))
310 return SpecialBoolConversion(value, input
, ref conversionSucceeded
);
312 else if (input
== null)
314 conversionSucceeded
= false;
317 else if (value == String
.Empty
)
319 conversionSucceeded
= true;
324 return System
.Convert
.ChangeType(input
, desiredType
);
328 private static object SpecialBoolConversion(string value, object input
, ref bool conversionSucceeded
)
332 conversionSucceeded
= false;
335 else if (value == String
.Empty
)
337 conversionSucceeded
= false;
342 if (value.IndexOf(',') != -1)
344 value = value.Substring(0, value.IndexOf(','));
347 bool performNumericConversion
= false;
349 foreach(char c
in value.ToCharArray())
351 if (Char
.IsNumber(c
))
353 performNumericConversion
= true;
358 if (performNumericConversion
)
360 return System
.Convert
.ToBoolean(System
.Convert
.ToInt32(value));
364 return !(String
.Compare("false", value, true) == 0);
369 private object ConvertToArray(Type desiredType
, object input
, ref bool conversionSucceeded
)
371 Type elemType
= desiredType
.GetElementType();
373 input
= FixInputForMonoIfNeeded(elemType
, input
);
375 Array values
= input
as Array
;
376 Array result
= Array
.CreateInstance(elemType
, values
.Length
);
378 for(int i
= 0; i
< values
.Length
; i
++)
380 bool elementConversionSucceeded
;
382 result
.SetValue(Convert(elemType
, values
.GetValue(i
), out elementConversionSucceeded
), i
);
384 // if at least one array element get converted
385 // we consider the conversion a success
386 if (elementConversionSucceeded
&& !conversionSucceeded
)
388 conversionSucceeded
= true;
395 private static bool IsBool(Type desiredType
)
397 bool isBool
= desiredType
== typeof(Boolean
);
401 isBool
= desiredType
== typeof(bool?);
407 private static object ConvertEnum(Type desiredType
, object input
, ref bool conversionSucceeded
)
409 conversionSucceeded
= true;
413 conversionSucceeded
= false;
417 String
value = NormalizeInput(input
);
419 if (value == String
.Empty
)
421 conversionSucceeded
= false;
426 return Enum
.Parse(desiredType
, value, true);
430 private static object ConvertDate(object input
, ref bool conversionSucceeded
)
432 conversionSucceeded
= true;
436 conversionSucceeded
= false;
440 String
value = NormalizeInput(input
);
442 if (value == String
.Empty
)
444 conversionSucceeded
= false;
450 return DateTime
.Parse(value);
455 /// Support for types that specify a TypeConverter,
456 /// i.e.: NullableTypes
458 private static object ConvertUsingTypeConverter(Type desiredType
, object input
, ref bool conversionSucceeded
)
460 conversionSucceeded
= true;
462 Type sourceType
= (input
!= null ? input
.GetType() : typeof(String
));
463 TypeConverter conv
= TypeDescriptor
.GetConverter(desiredType
);
465 if (conv
!= null && conv
.CanConvertFrom(sourceType
))
469 return conv
.ConvertFrom(input
);
473 String message
= String
.Format("Conversion error: " +
474 "Could not convert parameter with value '{0}' to {1}", input
, desiredType
);
476 throw new BindingException(message
, ex
);
481 String message
= String
.Format("Conversion error: " +
482 "Could not convert parameter with value '{0}' to {1}", input
, desiredType
);
484 throw new BindingException(message
);
488 private static String
NormalizeInput(object input
)
496 if (!input
.GetType().IsArray
)
498 return input
.ToString().Trim();
502 Array array
= (Array
) input
;
504 String
[] stArray
= new string[array
.GetLength(0)];
506 for(int i
= 0; i
< stArray
.Length
; i
++)
508 object itemVal
= array
.GetValue(i
);
512 stArray
[i
] = itemVal
.ToString();
516 stArray
[i
] = String
.Empty
;
520 return String
.Join(",", stArray
);
525 private static void ThrowInformativeException(Type desiredType
, object input
, Exception inner
)
527 String message
= String
.Format("Conversion error: " +
528 "Could not convert parameter with value '{0}' to expected type {1}", input
,
531 throw new BindingException(message
, inner
);