diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs new file mode 100644 index 0000000000..a71d0a3f1e --- /dev/null +++ b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Umbraco.Core.ObjectResolution +{ + internal abstract class LazyManyObjectsResolverBase : ManyObjectsResolverBase + where TResolved : class + where TResolver : class + { + #region Constructors + + protected LazyManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + : base(scope) + { + } + + protected LazyManyObjectsResolverBase(HttpContextBase httpContext) + : base(httpContext) + { + } + + protected LazyManyObjectsResolverBase(IEnumerable> value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + : this(scope) + { + AddTypes(value); + } + + protected LazyManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable> value) + : this(httpContext) + { + + } + #endregion + + private readonly List> _lazyTypes = new List>(); + private bool _hasResolvedTypes = false; + + /// + /// Used for unit tests + /// + internal bool HasResolvedTypes + { + get { return _hasResolvedTypes; } + } + + /// + /// Once this is called this will resolve all types registered in the lazy list + /// + protected override IEnumerable InstanceTypes + { + get + { + var list = _lazyTypes.Select(x => x.Value).ToArray(); + + //we need to validate each resolved type now since we could not do it before when inserting the lazy delegates + if (!_hasResolvedTypes) + { + var uniqueList = new List(); + foreach (var l in list) + { + EnsureCorrectType(l); + if (uniqueList.Contains(l)) + { + throw new InvalidOperationException("The Type " + l + " already exists in the collection"); + } + uniqueList.Add(l); + } + _hasResolvedTypes = true; + } + + return list; + } + } + + protected void AddTypes(IEnumerable> types) + { + EnsureAddSupport(); + + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) + { + foreach (var t in types) + { + _lazyTypes.Add(t); + } + } + } + + /// + /// Adds a lazy type to the list + /// + /// + public void AddType(Lazy value) + { + EnsureAddSupport(); + + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) + { + _lazyTypes.Add(value); + } + } + + /// + /// Converts the static type added to a lazy type and adds it to the internal list + /// + /// + public override void AddType(Type value) + { + AddType(new Lazy(() => value)); + } + + /// + /// Clears all lazy types + /// + public override void Clear() + { + EnsureClearSupport(); + + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) + { + _lazyTypes.Clear(); + } + } + + /// + /// Does not support removal + /// + protected override bool SupportsRemove + { + get { return false; } + } + + /// + /// Does not support insert + /// + protected override bool SupportsInsert + { + get { return false; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/MacroFieldEditorsResolver.cs b/src/Umbraco.Core/ObjectResolution/MacroFieldEditorsResolver.cs index 348ed699d3..9143e39f90 100644 --- a/src/Umbraco.Core/ObjectResolution/MacroFieldEditorsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/MacroFieldEditorsResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web; using System.Web.UI; using Umbraco.Core.Macros; @@ -56,7 +57,7 @@ namespace Umbraco.Core.ObjectResolution /// internal List MacroControlTypes { - get { return InstanceTypes; } + get { return InstanceTypes.ToList(); } } /// diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 2172fe9102..9343b1a58b 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 { - internal abstract class ManyObjectsResolverBase : ResolverBase where TResolved : class where TResolver : class { private List _applicationInstances = null; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - + private readonly List _instanceTypes = new List(); + #region Constructors @@ -34,7 +34,7 @@ namespace Umbraco.Core.ObjectResolution } LifetimeScope = scope; - InstanceTypes = new List(); + _instanceTypes = new List(); } /// @@ -48,7 +48,7 @@ namespace Umbraco.Core.ObjectResolution if (httpContext == null) throw new ArgumentNullException("httpContext"); LifetimeScope = ObjectLifetimeScope.HttpRequest; CurrentHttpContext = httpContext; - InstanceTypes = new List(); + _instanceTypes = new List(); } /// @@ -58,8 +58,8 @@ namespace Umbraco.Core.ObjectResolution /// If set to true will resolve singleton objects which will be created once for the lifetime of the application protected ManyObjectsResolverBase(IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(scope) - { - InstanceTypes = new List(value); + { + _instanceTypes = new List(value); } /// @@ -71,7 +71,7 @@ namespace Umbraco.Core.ObjectResolution protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable value) : this(httpContext) { - InstanceTypes = new List(value); + _instanceTypes = new List(value); } #endregion @@ -83,7 +83,10 @@ namespace Umbraco.Core.ObjectResolution /// /// Returns the list of Types registered that instances will be created from /// - protected List InstanceTypes { get; private set; } + protected virtual IEnumerable InstanceTypes + { + get { return _instanceTypes; } + } /// /// Returns the Current HttpContextBase used to construct this object if one exists. @@ -97,11 +100,7 @@ namespace Umbraco.Core.ObjectResolution protected ObjectLifetimeScope LifetimeScope { get; private set; } private int _defaultPluginWeight = 10; - private bool _supportsAdd = true; - private bool _supportsInsert = true; - private bool _supportsClear = true; - private bool _supportsRemove = true; - + /// /// Used in conjunction with GetSortedValues and WeightedPluginAttribute, if any of the objects /// being resolved do not contain the WeightedPluginAttribute then this will be the default weight applied @@ -189,15 +188,16 @@ namespace Umbraco.Core.ObjectResolution /// Removes a type. /// /// The type to remove. - public void RemoveType(Type value) + public virtual void RemoveType(Type value) { - if (!SupportsRemove) - throw new InvalidOperationException("This resolver does not support Removing types"); + EnsureRemoveSupport(); - using (new WriteLock(_lock)) + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) { EnsureCorrectType(value); - InstanceTypes.Remove(value); + _instanceTypes.Remove(value); } } @@ -215,8 +215,12 @@ namespace Umbraco.Core.ObjectResolution /// /// protected void AddTypes(IEnumerable types) - { - using (var l = new WriteLock(_lock)) + { + EnsureAddSupport(); + + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) { foreach(var t in types) { @@ -225,7 +229,7 @@ namespace Umbraco.Core.ObjectResolution { throw new InvalidOperationException("The Type " + t + " already exists in the collection"); }; - InstanceTypes.Add(t); + _instanceTypes.Add(t); } } } @@ -234,19 +238,20 @@ namespace Umbraco.Core.ObjectResolution /// Adds a Type to the end of the list. /// /// The object to be added. - public void AddType(Type value) + public virtual void AddType(Type value) { - if (!SupportsAdd) - throw new InvalidOperationException("This resolver does not support Adding new types"); + EnsureAddSupport(); - using (var l = new WriteLock(_lock)) + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) { EnsureCorrectType(value); if (InstanceTypes.Contains(value)) { throw new InvalidOperationException("The Type " + value + " already exists in the collection"); }; - InstanceTypes.Add(value); + _instanceTypes.Add(value); } } @@ -262,14 +267,15 @@ namespace Umbraco.Core.ObjectResolution /// /// Clears the list. /// - public void Clear() + public virtual void Clear() { - if (!SupportsClear) - throw new InvalidOperationException("This resolver does not support Clearing types"); + EnsureClearSupport(); - using (new WriteLock(_lock)) + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) { - InstanceTypes.Clear(); + _instanceTypes.Clear(); } } @@ -278,12 +284,13 @@ namespace Umbraco.Core.ObjectResolution /// /// The zero-based index at which the object should be inserted. /// The object to insert. - public void InsertType(int index, Type value) + public virtual void InsertType(int index, Type value) { - if (!SupportsInsert) - throw new InvalidOperationException("This resolver does not support Inserting new types"); + EnsureInsertSupport(); - using (var l = new UpgradeableReadLock(_lock)) + EnsureResolutionNotFrozen(); + + using (var l = GetWriteLock()) { EnsureCorrectType(value); if (InstanceTypes.Contains(value)) @@ -291,8 +298,7 @@ namespace Umbraco.Core.ObjectResolution throw new InvalidOperationException("The Type " + value + " already exists in the collection"); }; - l.UpgradeToWriteLock(); - InstanceTypes.Insert(index, value); + _instanceTypes.Insert(index, value); } } @@ -306,7 +312,65 @@ namespace Umbraco.Core.ObjectResolution InsertType(index, typeof (T)); } - private void EnsureCorrectType(Type t) + /// + /// Returns a WriteLock to use when modifying collections + /// + /// + protected WriteLock GetWriteLock() + { + return new WriteLock(_lock); + } + + /// + /// Throws an exception if resolution is frozen + /// + protected void EnsureResolutionNotFrozen() + { + if (Resolution.IsFrozen) + throw new InvalidOperationException("The type list cannot be modified after resolution has been frozen"); + } + + /// + /// Throws an exception if this does not support Remove + /// + protected void EnsureRemoveSupport() + { + if (!SupportsRemove) + throw new InvalidOperationException("This resolver does not support Removing types"); + } + + /// + /// Throws an exception if this does not support Clear + /// + protected void EnsureClearSupport() + { + if (!SupportsClear) + throw new InvalidOperationException("This resolver does not support Clearing types"); + } + + /// + /// Throws an exception if this does not support Add + /// + protected void EnsureAddSupport() + { + if (!SupportsAdd) + throw new InvalidOperationException("This resolver does not support Adding new types"); + } + + /// + /// Throws an exception if this does not support insert + /// + protected void EnsureInsertSupport() + { + if (!SupportsInsert) + throw new InvalidOperationException("This resolver does not support Inserting new types"); + } + + /// + /// Throws an exception if the type is not of the TResolved type + /// + /// + protected void EnsureCorrectType(Type t) { if (!TypeHelper.IsTypeAssignableFrom(t)) throw new InvalidOperationException("The resolver " + this.GetType() + " can only accept types of " + typeof(TResolved) + ". The Type passed in to this method is " + t); @@ -314,22 +378,22 @@ namespace Umbraco.Core.ObjectResolution protected virtual bool SupportsAdd { - get { return _supportsAdd; } + get { return true; } } protected virtual bool SupportsInsert { - get { return _supportsInsert; } + get { return true; } } protected virtual bool SupportsClear { - get { return _supportsClear; } + get { return true; } } protected virtual bool SupportsRemove { - get { return _supportsRemove; } + get { return true; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 672bed94a7..c4535c99d9 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs new file mode 100644 index 0000000000..56ed107e68 --- /dev/null +++ b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Tests.Resolvers +{ + [TestFixture] + public class LazyManyObjectResolverTests + { + + [SetUp] + public void Initialize() + { + + } + + [TearDown] + public void TearDown() + { + Resolution.IsFrozen = false; + } + + [Test] + public void Ensure_Lazy_Type_Resolution() + { + var resolver = new LazyResolver(new[] {new Lazy(() => typeof (TransientObject3))}); + resolver.AddType(); + resolver.AddType(new Lazy(() => typeof(TransientObject2))); + + Resolution.Freeze(); + + Assert.IsFalse(resolver.HasResolvedTypes); + + var instances1 = resolver.Objects; + + Assert.IsTrue(resolver.HasResolvedTypes); + + Assert.AreEqual(3, instances1.Count()); + Assert.IsTrue(instances1.Select(x => x.GetType()).ContainsAll(new []{typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3)})); + } + + #region Test classes + + private interface ITestInterface + { + } + + private class TransientObject1 : ITestInterface + { + } + + private class TransientObject2 : ITestInterface + { + } + + private class TransientObject3 : ITestInterface + { + } + + private sealed class LazyResolver : LazyManyObjectsResolverBase + { + public LazyResolver() + : base(ObjectLifetimeScope.Transient) + { + } + + public LazyResolver(IEnumerable> values) + :base (values, ObjectLifetimeScope.Transient) + { + + } + + public IEnumerable Objects + { + get { return Values; } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Resolvers/ManyObjectResolverTests.cs b/src/Umbraco.Tests/Resolvers/ManyObjectResolverTests.cs index 53ca92e486..20f970384e 100644 --- a/src/Umbraco.Tests/Resolvers/ManyObjectResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/ManyObjectResolverTests.cs @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Resolvers Assert.IsFalse(object.ReferenceEquals(instances1.Single(), instances3.Single())); } - #region + #region Test classes private interface ITestInterface { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ae497719f9..c7bfb8cba0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -86,6 +86,7 @@ +