diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs index 76c8f75076..002639e40d 100644 --- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs +++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs @@ -22,8 +22,8 @@ namespace Umbraco.Core if (value is string) { var str = (string)value; + if (str == null || str.Length == 0 || str == "0") return false; if (str == "1") return true; - if (str == "0" || str == "") return false; } return base.ConvertFrom(context, culture, value); diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index b707163255..605a6d7847 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -16,7 +16,6 @@ namespace Umbraco.Core { public static class ObjectExtensions { - //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); public static IEnumerable AsEnumerableOfOne(this T input) @@ -86,8 +85,30 @@ namespace Umbraco.Core //check for string so that overloaders of ToString() can take advantage of the conversion. if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); - if (!destinationType.IsGenericType || destinationType.GetGenericTypeDefinition() != typeof(Nullable<>)) + // if we've got a nullable of something, we try to convert directly to that thing. + if (destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>)) { + // recursively call into myself with the inner (not-nullable) type and handle the outcome + var nonNullable = input.TryConvertTo(Nullable.GetUnderlyingType(destinationType)); + + // 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. @@ -151,7 +172,6 @@ namespace Umbraco.Core } } - if (TypeHelper.IsTypeAssignableFrom(input)) { try @@ -168,6 +188,119 @@ namespace Umbraco.Core return Attempt.Fail(); } + private static Nullable> TryConvertToFromString(this string input, Type destinationType) + { + if (destinationType == typeof(string)) + return Attempt.Succeed(input); + + if (input == null || input.Length == 0) + { + if (destinationType == typeof(Boolean)) + return Attempt.Succeed(false); // special case for booleans, null/empty == false + else 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(); + } + else if (destinationType == typeof(Int64)) + { + Int64 value; + return Int64.TryParse(input, out value) ? Attempt.Succeed(value) : Attempt.Fail(); + } + else 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 @@ -289,7 +422,7 @@ namespace Umbraco.Core { return "\"{0}\"".InvariantFormat(obj); } - if (obj is int || obj is Int16 || obj is Int64 || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is double? || obj is bool?) + 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); } @@ -416,6 +549,5 @@ namespace Umbraco.Core return "[GetPropertyValueException]"; } } - } } \ No newline at end of file diff --git a/src/Umbraco.Tests/ObjectExtensionsTests.cs b/src/Umbraco.Tests/ObjectExtensionsTests.cs index 6829e61a4f..aa433e40ec 100644 --- a/src/Umbraco.Tests/ObjectExtensionsTests.cs +++ b/src/Umbraco.Tests/ObjectExtensionsTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Threading; using NUnit.Framework; using Umbraco.Core; using Umbraco.Tests.PartialTrust; @@ -83,8 +85,8 @@ namespace Umbraco.Tests { var result = testCase.Key.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(testCase.Value, result.Result); + Assert.IsTrue(result.Success, testCase.Key); + Assert.AreEqual(testCase.Value, result.Result, testCase.Key); } } @@ -96,39 +98,50 @@ namespace Umbraco.Tests { {"2012-11-10", true}, {"2012/11/10", true}, - {"10/11/2012", true}, - {"11/10/2012", false}, + {"10/11/2012", true}, // assuming your culture uses DD/MM/YYYY + {"11/10/2012", false}, // assuming your culture uses DD/MM/YYYY {"Sat 10, Nov 2012", true}, {"Saturday 10, Nov 2012", true}, {"Sat 10, November 2012", true}, {"Saturday 10, November 2012", true}, - {"2012-11-10 13:14:15", true}, - {"", false} + {"2012-11-10 13:14:15", true} }; foreach (var testCase in testCases) { var result = testCase.Key.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), testCase.Value); + Assert.IsTrue(result.Success, testCase.Key); + Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), testCase.Value, testCase.Key); } } - [Test] - public virtual void CanConvertObjectToString_Using_ToString_Overload() - { - var result = new MyTestObject().TryConvertTo(); + [Test] + public virtual void CanConvertBlankStringToDateTime() + { + var result = "".TryConvertTo(); + Assert.IsTrue(result.Success); + Assert.AreEqual(DateTime.MinValue, result.Result); + } - Assert.IsTrue(result.Success); - Assert.AreEqual("Hello world", result.Result); - } + [Test] + public virtual void CanConvertObjectToString_Using_ToString_Overload() + { + var result = new MyTestObject().TryConvertTo(); + + Assert.IsTrue(result.Success); + Assert.AreEqual("Hello world", result.Result); + } + + private CultureInfo savedCulture; /// /// Run once before each test in derived test fixtures. /// public override void TestSetup() { + savedCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB"); // make sure the dates parse correctly return; } @@ -137,6 +150,7 @@ namespace Umbraco.Tests /// public override void TestTearDown() { + Thread.CurrentThread.CurrentCulture = savedCulture; return; }