From 4d5b3305679dae539267714340b0c5ce88c6a778 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Thu, 17 Oct 2013 19:12:08 -0500 Subject: [PATCH 1/2] Reduce Exceptions thrown during type conversions. There are tons of places where null, blank or empty strings are being converted to primative types (like int,, long, etc) or other ValueTypes (like Guid, DateTime, etc.). When this is attempted, Exceptions are thrown (and caught/swallowed) to allow for value defaulting. Since this happens all the time in normal operation, tons of Exceptions are being thrown. By switching to the TryParse methods available on all primative types and the interesting ValueTypes, we can greatly speed the processing of content data. --- .../CustomBooleanTypeConverter.cs | 2 +- src/Umbraco.Core/ObjectExtensions.cs | 142 +++++++++++++++++- src/Umbraco.Tests/ObjectExtensionsTests.cs | 44 ++++-- 3 files changed, 167 insertions(+), 21 deletions(-) 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; } From d20ea584673d5cc8f82b3f9fa9772547a24e6bab Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 18 Oct 2013 16:13:41 +1100 Subject: [PATCH 2/2] Fixes: U4-3193 Partial view cache needs to invalidate based on cache refreshers --- src/Umbraco.Tests/TypeHelperTests.cs | 3 ++- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 2 ++ src/Umbraco.Web/Cache/MemberCacheRefresher.cs | 2 ++ src/Umbraco.Web/Cache/PageCacheRefresher.cs | 5 ++++ src/Umbraco.Web/CacheHelperExtensions.cs | 26 ------------------- src/Umbraco.Web/WebBootManager.cs | 1 - 6 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Tests/TypeHelperTests.cs b/src/Umbraco.Tests/TypeHelperTests.cs index 93bde4439c..f637b1ff20 100644 --- a/src/Umbraco.Tests/TypeHelperTests.cs +++ b/src/Umbraco.Tests/TypeHelperTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Tests.PartialTrust; using Umbraco.Web; +using Umbraco.Web.Cache; using UmbracoExamine; using umbraco; using umbraco.presentation; @@ -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 366607afd2..8845d61657 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; @@ -60,6 +61,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(); @@ -72,6 +74,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(); @@ -80,6 +83,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(); @@ -88,6 +92,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 780f841190..0dfed94888 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -18,32 +18,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"; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 31d3ce42ed..a75b9a2bcd 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -116,7 +116,6 @@ namespace Umbraco.Web protected override void InitializeApplicationEventsResolver() { base.InitializeApplicationEventsResolver(); - ApplicationEventsResolver.Current.AddType(); ApplicationEventsResolver.Current.AddType(); //We need to remove these types because we've obsoleted them and we don't want them executing: ApplicationEventsResolver.Current.RemoveType();