From 164f22aa5003241a32604873ec58b5b5b495197d Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 12 Mar 2013 01:50:56 +0400 Subject: [PATCH] Makes ApplicationContext disposable so that 3rd party devs are able to reset their applications (or unit tests), updated resolution and created a ResolverCollection to track all resolvers created so it's super easy to reset all of them at once (which is what happens on ApplicationContext.Dispose. ApplicationContext.Dispose is also implicitly implemented as to not show in intellisense that the Dispose method exists... must be cast to IDisposable to work. --- src/Umbraco.Core/ApplicationContext.cs | 37 +++++++++- .../LazyManyObjectsResolverbase.cs | 2 +- .../LegacyTransientObjectsResolver.cs | 2 +- .../ManyObjectsResolverBase.cs | 6 +- .../ObjectResolution/ResolverBase.cs | 59 ++++++++++++---- .../ObjectResolution/ResolverCollection.cs | 69 +++++++++++++++++++ .../SingleObjectResolverBase.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Resolvers/ResolutionTests.cs | 50 +++++++++++++- 9 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 src/Umbraco.Core/ObjectResolution/ResolverCollection.cs 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(); + } } }