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 96e402a9b2..875cd35910 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) @@ -93,8 +92,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. @@ -158,7 +179,6 @@ namespace Umbraco.Core } } - if (TypeHelper.IsTypeAssignableFrom(input)) { try @@ -175,6 +195,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 @@ -296,7 +429,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); } @@ -435,6 +568,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 72c913a75f..e4f456bec8 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.TestHelpers; @@ -82,8 +84,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); } } @@ -95,34 +97,41 @@ 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(); - [Test] + Assert.IsTrue(result.Success); + Assert.AreEqual("Hello world", result.Result); + } + + [Test] public virtual void CanConvertObjectToSameObject() { var obj = new MyTestObject(); @@ -130,7 +139,27 @@ namespace Umbraco.Tests Assert.AreEqual(obj, 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; + } + + /// + /// Run once after each test in derived test fixtures. + /// + public override void TestTearDown() + { + Thread.CurrentThread.CurrentCulture = savedCulture; + return; + } private class MyTestObject { diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs index d1017e9546..42e5b0bdd2 100644 --- a/src/Umbraco.Tests/TypeHelperTests.cs +++ b/src/Umbraco.Tests/TypeHelperTests.cs @@ -7,6 +7,7 @@ using System.Data.SqlClient; using NUnit.Framework; using Umbraco.Core; using Umbraco.Web; +using Umbraco.Web.Cache; using UmbracoExamine; using UmbracoExamine.DataServices; using umbraco; @@ -67,7 +68,7 @@ namespace Umbraco.Tests var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), typeof (LegacyScheduledTasks), - typeof(CacheHelperExtensions.CacheHelperApplicationEventListener)); + typeof(CacheRefresherEventHandler)); Assert.IsTrue(t6.Success); Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 2536165057..53abacc7a5 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -111,6 +111,8 @@ namespace Umbraco.Web.Cache { if (payloads == null) return; + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + payloads.ForEach(payload => { foreach (var idPart in payload.Path.Split(',')) diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index 4780a9e502..8f6437c8b6 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -44,6 +44,8 @@ namespace Umbraco.Web.Cache private void ClearCache(int id) { + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + ApplicationContext.Current.ApplicationCache. ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.MemberLibraryCacheKey, id)); ApplicationContext.Current.ApplicationCache. diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index 3c9e496dec..ca20d5c2de 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Sync; @@ -58,6 +59,7 @@ namespace Umbraco.Web.Cache /// The id. public override void Refresh(int id) { + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.UpdateDocumentCache(id); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); @@ -70,6 +72,7 @@ namespace Umbraco.Web.Cache /// The id. public override void Remove(int id) { + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.ClearDocumentCache(id); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); @@ -78,6 +81,7 @@ namespace Umbraco.Web.Cache public override void Refresh(IContent instance) { + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.UpdateDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); @@ -86,6 +90,7 @@ namespace Umbraco.Web.Cache public override void Remove(IContent instance) { + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); content.Instance.ClearDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index 3a5aaaaf6c..9324e48cd2 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -14,32 +14,6 @@ namespace Umbraco.Web /// internal static class CacheHelperExtensions { - /// - /// Application event handler to bind to events to clear the cache for the cache helper extensions - /// - internal sealed class CacheHelperApplicationEventListener : ApplicationEventHandler - { - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (applicationContext != null) - { - //bind to events to clear the cache, after publish, after media save and after member save - - Document.AfterPublish - += (sender, args) => - applicationContext.ApplicationCache.ClearPartialViewCache(); - - global::umbraco.cms.businesslogic.media.Media.AfterSave - += (sender, args) => - applicationContext.ApplicationCache.ClearPartialViewCache(); - - global::umbraco.cms.businesslogic.member.Member.AfterSave - += (sender, args) => - applicationContext.ApplicationCache.ClearPartialViewCache(); - } - } - - } public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey";