using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Security; using System.Xml; namespace Umbraco.Core { public static class ObjectExtensions { //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); public static IEnumerable AsEnumerableOfOne(this T input) { return Enumerable.Repeat(input, 1); } public static void DisposeIfDisposable(this object input) { var disposable = input as IDisposable; if (disposable != null) disposable.Dispose(); } /// /// Provides a shortcut way of safely casting an input when you cannot guarantee the is an instance type (i.e., when the C# AS keyword is not applicable) /// /// /// The input. /// internal static T SafeCast(this object input) { if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default(T); if (input is T) return (T)input; return default(T); } /// /// Tries to convert the input object to the output type using TypeConverters /// /// /// /// public static Attempt TryConvertTo(this object input) { var result = TryConvertTo(input, typeof(T)); if (!result.Success) { //just try a straight up conversion try { var converted = (T) input; return Attempt.Succeed(converted); } catch (Exception e) { return Attempt.Fail(e); } } return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result); } /// /// Tries to convert the input object to the output type using TypeConverters. If the destination type is a superclass of the input type, /// if will use . /// /// The input. /// Type of the destination. /// public static Attempt TryConvertTo(this object input, Type destinationType) { if (input == null) return Attempt.Fail(); if (destinationType == typeof(object)) return Attempt.Succeed(input); if (input.GetType() == destinationType) return Attempt.Succeed(input); //check for string so that overloaders of ToString() can take advantage of the conversion. if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); // if we've got a nullable of something, we try to convert directly to that thing. if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var underlyingType = Nullable.GetUnderlyingType(destinationType); //special case for empty strings for bools/dates which should return null if an empty string var asString = input as string; if (asString != null && string.IsNullOrEmpty(asString) && (underlyingType == typeof(DateTime) || underlyingType == typeof(bool))) { return Attempt.Succeed(null); } // recursively call into myself with the inner (not-nullable) type and handle the outcome var nonNullable = input.TryConvertTo(underlyingType); // and if sucessful, fall on through to rewrap in a nullable; if failed, pass on the exception if (nonNullable.Success) input = nonNullable.Result; // now fall on through... else return Attempt.Fail(nonNullable.Exception); } // we've already dealed with nullables, so any other generic types need to fall through if (!destinationType.IsGenericType) { if (input is string) { var result = TryConvertToFromString(input as string, destinationType); // if we processed the string (succeed or fail), we're done if (result.HasValue) return result.Value; } //TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. if (TypeHelper.IsTypeAssignableFrom(destinationType, input.GetType()) && TypeHelper.IsTypeAssignableFrom(input)) { try { var casted = Convert.ChangeType(input, destinationType); return Attempt.Succeed(casted); } catch (Exception e) { return Attempt.Fail(e); } } } var inputConverter = TypeDescriptor.GetConverter(input); if (inputConverter.CanConvertTo(destinationType)) { try { var converted = inputConverter.ConvertTo(input, destinationType); return Attempt.Succeed(converted); } catch (Exception e) { return Attempt.Fail(e); } } if (destinationType == typeof(bool)) { var boolConverter = new CustomBooleanTypeConverter(); if (boolConverter.CanConvertFrom(input.GetType())) { try { var converted = boolConverter.ConvertFrom(input); return Attempt.Succeed(converted); } catch (Exception e) { return Attempt.Fail(e); } } } var outputConverter = TypeDescriptor.GetConverter(destinationType); if (outputConverter.CanConvertFrom(input.GetType())) { try { var converted = outputConverter.ConvertFrom(input); return Attempt.Succeed(converted); } catch (Exception e) { return Attempt.Fail(e); } } if (TypeHelper.IsTypeAssignableFrom(input)) { try { var casted = Convert.ChangeType(input, destinationType); return Attempt.Succeed(casted); } catch (Exception e) { return Attempt.Fail(e); } } return Attempt.Fail(); } private static Nullable> TryConvertToFromString(this string input, Type destinationType) { if (destinationType == typeof(string)) return Attempt.Succeed(input); if (string.IsNullOrEmpty(input)) { if (destinationType == typeof(Boolean)) return Attempt.Succeed(false); // special case for booleans, null/empty == false if (destinationType == typeof(DateTime)) return Attempt.Succeed(DateTime.MinValue); } // we have a non-empty string, look for type conversions in the expected order of frequency of use... if (destinationType.IsPrimitive) { if (destinationType == typeof(Int32)) { Int32 value; return Int32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } if (destinationType == typeof(Int64)) { Int64 value; return Int64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } if (destinationType == typeof(Boolean)) { Boolean value; if (Boolean.TryParse(input, out value)) return Attempt.Succeed(value); // don't declare failure so the CustomBooleanTypeConverter can try } else if (destinationType == typeof(Int16)) { Int16 value; return Int16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Double)) { Double value; return Double.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Single)) { Single value; return Single.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Char)) { Char value; return Char.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Byte)) { Byte value; return Byte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(SByte)) { SByte value; return SByte.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(UInt32)) { UInt32 value; return UInt32.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(UInt16)) { UInt16 value; return UInt16.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(UInt64)) { UInt64 value; return UInt64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } } else if (destinationType == typeof(Guid)) { Guid value; return Guid.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(DateTime)) { DateTime value; return DateTime.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(DateTimeOffset)) { DateTimeOffset value; return DateTimeOffset.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(TimeSpan)) { TimeSpan value; return TimeSpan.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Decimal)) { Decimal value; return Decimal.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } else if (destinationType == typeof(Version)) { Version value; return Version.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); } // E_NOTIMPL IPAddress, BigInteger return null; // we can't decide... } internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) { //TODO: Localise this exception if (isDisposed) throw new ObjectDisposedException(objectname); } //public enum PropertyNamesCaseType //{ // CamelCase, // CaseInsensitive //} ///// ///// Convert an object to a JSON string with camelCase formatting ///// ///// ///// //public static string ToJsonString(this object obj) //{ // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); //} ///// ///// Convert an object to a JSON string with the specified formatting ///// ///// The obj. ///// Type of the property names case. ///// //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) //{ // var type = obj.GetType(); // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) // { // return obj.ToString(); // } // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) // { // return Convert.ToDateTime(obj).ToString(dateTimeStyle); // } // var serializer = new JsonSerializer(); // switch (propertyNamesCaseType) // { // case PropertyNamesCaseType.CamelCase: // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); // break; // } // var dateTimeConverter = new IsoDateTimeConverter // { // DateTimeStyles = System.Globalization.DateTimeStyles.None, // DateTimeFormat = dateTimeStyle // }; // if (typeof(IDictionary).IsAssignableFrom(type)) // { // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); // } // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) // { // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); // } // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); //} /// /// Converts an object into a dictionary /// /// /// /// /// /// /// internal static IDictionary ToDictionary(this T o, params Expression>[] ignoreProperties) { return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); } /// /// Turns object into dictionary /// /// /// Properties to ignore /// internal static IDictionary ToDictionary(this object o, params string[] ignoreProperties) { if (o != null) { var props = TypeDescriptor.GetProperties(o); var d = new Dictionary(); foreach (var prop in props.Cast().Where(x => !ignoreProperties.Contains(x.Name))) { var val = prop.GetValue(o); if (val != null) { d.Add(prop.Name, (TVal)val); } } return d; } return new Dictionary(); } internal static string ToDebugString(this object obj, int levels = 0) { if (obj == null) return "{null}"; try { if (obj is string) { return "\"{0}\"".InvariantFormat(obj); } if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?) { return "{0}".InvariantFormat(obj); } if (obj is Enum) { return "[{0}]".InvariantFormat(obj); } if (obj is IEnumerable) { var enumerable = (obj as IEnumerable); var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); return items.Count() > 0 ? "{{ {0} }}".InvariantFormat(String.Join(", ", items)) : null; } var props = obj.GetType().GetProperties(); if ((props.Count() == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) { try { var key = props[0].GetValue(obj, null) as string; var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); return "{0}={1}".InvariantFormat(key, value); } catch (Exception) { return "[KeyValuePropertyException]"; } } if (levels > -1) { var items = from propertyInfo in props let value = GetPropertyDebugString(propertyInfo, obj, levels) where value != null select "{0}={1}".InvariantFormat(propertyInfo.Name, value); return items.Count() > 0 ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items)) : null; } } catch (Exception ex) { return "[Exception:{0}]".InvariantFormat(ex.Message); } return null; } /// /// Attempts to serialize the value to an XmlString using ToXmlString /// /// /// /// internal static Attempt TryConvertToXmlString(this object value, Type type) { try { var output = value.ToXmlString(type); return Attempt.Succeed(output); } catch (NotSupportedException ex) { return Attempt.Fail(ex); } } /// /// Returns an XmlSerialized safe string representation for the value /// /// /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown /// internal static string ToXmlString(this object value, Type type) { if (type == typeof(string)) return ((string)value).IsNullOrWhiteSpace() ? "" : (string)value; if (type == typeof(bool)) return XmlConvert.ToString((bool)value); if (type == typeof(byte)) return XmlConvert.ToString((byte)value); if (type == typeof(char)) return XmlConvert.ToString((char)value); if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.RoundtripKind); if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value); if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value); if (type == typeof(double)) return XmlConvert.ToString((double)value); if (type == typeof(float)) return XmlConvert.ToString((float)value); if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value); if (type == typeof(int)) return XmlConvert.ToString((int)value); if (type == typeof(long)) return XmlConvert.ToString((long)value); if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value); if (type == typeof(short)) return XmlConvert.ToString((short)value); if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value); if (type == typeof(bool)) return XmlConvert.ToString((bool)value); if (type == typeof(uint)) return XmlConvert.ToString((uint)value); if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value); if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value); throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert"); } private static string GetEnumPropertyDebugString(object enumItem, int levels) { try { return enumItem.ToDebugString(levels - 1); } catch (Exception) { return "[GetEnumPartException]"; } } private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels) { try { return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1); } catch (Exception) { return "[GetPropertyValueException]"; } } } }