diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 776714b839..db58fb7858 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -1,9 +1,11 @@ using System; using System.Configuration; +using System.Threading; using System.Web; using System.Web.Caching; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; using Umbraco.Core.Services; @@ -15,7 +17,7 @@ namespace Umbraco.Core /// /// one per AppDomain, represents the global Umbraco application /// - public class ApplicationContext + public class ApplicationContext : IDisposable { /// /// Constructor @@ -186,5 +188,38 @@ namespace Umbraco.Core } internal set { _services = value; } } + + + private volatile bool _disposed; + private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); + + /// + /// This will dispose and reset all resources used to run the application + /// + /// + /// IMPORTANT: Never dispose this object if you require the Umbraco application to run, disposing this object + /// is generally used for unit testing and when your application is shutting down after you have booted Umbraco. + /// + void IDisposable.Dispose() + { + // Only operate if we haven't already disposed + if (_disposed) return; + + using (new WriteLock(_disposalLocker)) + { + // Check again now we're inside the lock + if (_disposed) return; + + //First we'll reset all resolvers + ResolverCollection.ResetAll(); + //Next resolution itself (though this should be taken care of by resetting any of the resolvers above) + Resolution.Reset(); + //Next, lets reset the plugin manager + PluginManager.Current = null; + + // Indicate that the instance has been disposed. + _disposed = true; + } + } } } diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs index f8476c790c..6aaf59106c 100644 --- a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs +++ b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.ObjectResolution /// internal abstract class LazyManyObjectsResolverBase : ManyObjectsResolverBase where TResolved : class - where TResolver : class + where TResolver : ResolverBase { #region Constructors diff --git a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs index eb871d4b73..9362ed54fe 100644 --- a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.ObjectResolution /// internal abstract class LegacyTransientObjectsResolver : LazyManyObjectsResolverBase where TResolved : class - where TResolver : class + where TResolver : ResolverBase { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private ConcurrentDictionary _id2type; diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 1a13f866d0..4a5f7cbd99 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -6,14 +6,14 @@ using System.Web; namespace Umbraco.Core.ObjectResolution { - /// + /// /// The base class for all many-objects resolvers. /// /// The type of the concrete resolver class. /// The type of the resolved objects. public abstract class ManyObjectsResolverBase : ResolverBase - where TResolved : class - where TResolver : class + where TResolved : class + where TResolver : ResolverBase { private IEnumerable _applicationInstances = null; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); diff --git a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ResolverBase.cs index 1e542eae59..fd22a76610 100644 --- a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ResolverBase.cs @@ -3,26 +3,52 @@ using System.Threading; namespace Umbraco.Core.ObjectResolution { + /// + /// Base non-generic class for resolvers + /// + public abstract class ResolverBase + { + protected ResolverBase(Action resetAction) + { + //add itself to the internal collection + ResolverCollection.Add(this, resetAction); + } + + } + /// /// The base class for all resolvers. /// /// The type of the concrete resolver class. /// Provides singleton management to all resolvers. - public abstract class ResolverBase - where TResolver : class + public abstract class ResolverBase : ResolverBase + where TResolver : ResolverBase { - static TResolver _resolver; - - /// - /// The lock for the singleton. - /// - /// - /// Though resharper says this is in error, it is actually correct. We want a different lock object for each generic type. - /// See this for details: http://confluence.jetbrains.net/display/ReSharper/Static+field+in+generic+type - /// - static readonly ReaderWriterLockSlim ResolversLock = new ReaderWriterLockSlim(); - /// + /// + /// The underlying singleton object instance + /// + static TResolver _resolver; + + /// + /// The lock for the singleton. + /// + /// + /// Though resharper says this is in error, it is actually correct. We want a different lock object for each generic type. + /// See this for details: http://confluence.jetbrains.net/display/ReSharper/Static+field+in+generic+type + /// + static readonly ReaderWriterLockSlim ResolversLock = new ReaderWriterLockSlim(); + + /// + /// Constructor set the reset action for the underlying object + /// + protected ResolverBase() + : base(() => Reset()) + { + + } + + /// /// Gets or sets the resolver singleton instance. /// /// The value can be set only once, and cannot be read before it has been set. @@ -84,15 +110,18 @@ namespace Umbraco.Core.ObjectResolution //In order to reset a resolver, we always must reset the resolution if (resetResolution) { - Resolution.Reset(); + Resolution.Reset(); } - + + //ensure its removed from the collection + ResolverCollection.Remove(_resolver); using (Resolution.Configuration) using (new WriteLock(ResolversLock)) { _resolver = null; } + } } } diff --git a/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs b/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs new file mode 100644 index 0000000000..34a7ff68bc --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Umbraco.Core.ObjectResolution +{ + /// + /// Simply used to track all ManyObjectsResolverBase instances so that we can + /// reset them all at once really easily. + /// + /// + /// Normally we'd use TypeFinding for this but because many of the resolvers are internal this won't work. + /// We'd rather not keep a static list of them so we'll dynamically add to this list based on the base + /// class of the ManyObjectsResolverBase. + /// + internal static class ResolverCollection + { + private static readonly ConcurrentDictionary Resolvers = new ConcurrentDictionary(); + + /// + /// Returns the number of resolvers created + /// + internal static int Count + { + get { return Resolvers.Count; } + } + + /// + /// Resets all resolvers + /// + internal static void ResetAll() + { + //take out each item from the bag and reset it + var keys = Resolvers.Keys.ToArray(); + foreach (var k in keys) + { + Action resetAction; + while (Resolvers.TryRemove(k, out resetAction)) + { + //call the reset action for the resolver + resetAction(); + } + } + } + + /// + /// This is called when the static Reset method or a ResolverBase{T} is called. + /// + internal static void Remove(ResolverBase resolver) + { + if (resolver == null) return; + Action action; + Resolvers.TryRemove(resolver, out action); + } + + /// + /// Adds a resolver to the collection + /// + /// + /// + /// + /// This is called when the creation of a ResolverBase occurs + /// + internal static void Add(ResolverBase resolver, Action resetAction) + { + Resolvers.TryAdd(resolver, resetAction); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs b/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs index 224467db6f..5d0e57c8f9 100644 --- a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.ObjectResolution /// public abstract class SingleObjectResolverBase : ResolverBase where TResolved : class - where TResolver : class + where TResolver : ResolverBase { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly bool _canBeNull; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7c1302648f..69ad1f60e6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -206,6 +206,7 @@ + diff --git a/src/Umbraco.Tests/Resolvers/ResolutionTests.cs b/src/Umbraco.Tests/Resolvers/ResolutionTests.cs index 2debaa1bd4..8e9da977a2 100644 --- a/src/Umbraco.Tests/Resolvers/ResolutionTests.cs +++ b/src/Umbraco.Tests/Resolvers/ResolutionTests.cs @@ -18,14 +18,14 @@ namespace Umbraco.Tests.Resolvers public void Setup() { TestHelper.SetupLog4NetForTests(); - - BaseResolver.Reset(); } [TearDown] public void TearDown() { BaseResolver.Reset(); + BaseResolver2.Reset(); + BaseResolver3.Reset(); } #region Resolvers and Resolved @@ -33,6 +33,12 @@ namespace Umbraco.Tests.Resolvers class BaseResolver : ResolverBase { } + class BaseResolver2 : ResolverBase + { } + + class BaseResolver3 : ResolverBase + { } + #endregion #region Test Resolution @@ -215,5 +221,45 @@ namespace Umbraco.Tests.Resolvers } #endregion + + [Test] + public void Resolver_Collection_Is_Updated() + { + BaseResolver.Current = new BaseResolver(); + BaseResolver2.Current = new BaseResolver2(); + BaseResolver3.Current = new BaseResolver3(); + Assert.AreEqual(3, ResolverCollection.Count); + } + + [Test] + public void Resolver_Collection_Is_Reset() + { + BaseResolver.Current = new BaseResolver(); + BaseResolver2.Current = new BaseResolver2(); + BaseResolver3.Current = new BaseResolver3(); + + ResolverCollection.ResetAll(); + + Assert.AreEqual(0, ResolverCollection.Count); + Assert.Throws(() => + { + var c = BaseResolver.Current; + }); + Assert.Throws(() => + { + var c = BaseResolver2.Current; + }); + Assert.Throws(() => + { + var c = BaseResolver3.Current; + }); + + //this should not error! + BaseResolver.Current = new BaseResolver(); + BaseResolver2.Current = new BaseResolver2(); + BaseResolver3.Current = new BaseResolver3(); + + Assert.Pass(); + } } }