diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs index a71d0a3f1e..0cd5705aa5 100644 --- a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs +++ b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs @@ -5,6 +5,19 @@ using System.Web; namespace Umbraco.Core.ObjectResolution { + /// + /// A base class for lazily resolving types for a resolver + /// + /// + /// + /// + /// This is a special case resolver for when types get lazily resolved in order to resolve the actual types. This is useful + /// for when there is some processing overhead (i.e. Type finding in assemblies) to return the Types used to instantiate the instances. + /// In some these cases we don't want to have to type find during application startup, only when we need to resolve the instances. + /// + /// Important notes about this resolver: This does not support Insert or Remove and therefore does not support any ordering unless + /// the types are marked with the WeightedPluginAttribute. + /// internal abstract class LazyManyObjectsResolverBase : ManyObjectsResolverBase where TResolved : class where TResolver : class @@ -21,28 +34,62 @@ namespace Umbraco.Core.ObjectResolution { } - protected LazyManyObjectsResolverBase(IEnumerable> value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + /// + /// Constructor accepting a list of lazy types + /// + /// + /// + protected LazyManyObjectsResolverBase(IEnumerable> listOfLazyTypes, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) : this(scope) { - AddTypes(value); + AddTypes(listOfLazyTypes); } - protected LazyManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable> value) + /// + /// Constructor accepting a delegate to return a list of types + /// + /// + /// + protected LazyManyObjectsResolverBase(Func> typeListDelegate, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) + : this(scope) + { + _listOfTypeListDelegates.Add(typeListDelegate); + } + + /// + /// Constructor accepting a list of lazy types + /// + /// + /// + protected LazyManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable> listOfLazyTypes) : this(httpContext) { - + AddTypes(listOfLazyTypes); + } + + /// + /// Constructor accepting a delegate to return a list of types + /// + /// + /// + protected LazyManyObjectsResolverBase(HttpContextBase httpContext, Func> typeListDelegate) + : this(httpContext) + { + _listOfTypeListDelegates.Add(typeListDelegate); } + #endregion - private readonly List> _lazyTypes = new List>(); - private bool _hasResolvedTypes = false; + private readonly List> _lazyTypeList = new List>(); + private readonly List>> _listOfTypeListDelegates = new List>>(); + private List _resolvedTypes = null; /// /// Used for unit tests /// internal bool HasResolvedTypes { - get { return _hasResolvedTypes; } + get { return _resolvedTypes != null; } } /// @@ -52,28 +99,50 @@ namespace Umbraco.Core.ObjectResolution { 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) + using (var lck = GetUpgradeableReadLock()) { - 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; - } + var lazyTypeList = _lazyTypeList.Select(x => x.Value).ToArray(); + var listofTypeListDelegates = _listOfTypeListDelegates.SelectMany(x => x()).ToArray(); - return list; + //we need to validate each resolved type now since we could not do it before when inserting the lazy delegates + if (!HasResolvedTypes) + { + lck.UpgradeToWriteLock(); + + _resolvedTypes = new List(); + + //first iterate the lazy type list + foreach (var l in lazyTypeList) + { + UpdateUniqueList(_resolvedTypes, l); + } + + //next iterate the list of list type delegates + foreach (var l in listofTypeListDelegates) + { + UpdateUniqueList(_resolvedTypes, l); + } + } + + return _resolvedTypes; + } } } + private void UpdateUniqueList(List uniqueList, Type toAdd) + { + EnsureCorrectType(toAdd); + if (uniqueList.Contains(toAdd)) + { + throw new InvalidOperationException("The Type " + toAdd + " already exists in the collection"); + } + uniqueList.Add(toAdd); + } + + /// + /// Allows adding of multiple lazy types at once + /// + /// protected void AddTypes(IEnumerable> types) { EnsureAddSupport(); @@ -84,11 +153,27 @@ namespace Umbraco.Core.ObjectResolution { foreach (var t in types) { - _lazyTypes.Add(t); + _lazyTypeList.Add(t); } } } + /// + /// Adds a type list delegate to the collection + /// + /// + public void AddTypeListDelegate(Func> typeListDelegate) + { + EnsureAddSupport(); + + EnsureResolutionNotFrozen(); + + using (GetWriteLock()) + { + _listOfTypeListDelegates.Add(typeListDelegate); + } + } + /// /// Adds a lazy type to the list /// @@ -101,7 +186,7 @@ namespace Umbraco.Core.ObjectResolution using (GetWriteLock()) { - _lazyTypes.Add(value); + _lazyTypeList.Add(value); } } @@ -125,7 +210,7 @@ namespace Umbraco.Core.ObjectResolution using (GetWriteLock()) { - _lazyTypes.Clear(); + _lazyTypeList.Clear(); } } diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 9343b1a58b..c2fb584602 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -320,7 +320,16 @@ namespace Umbraco.Core.ObjectResolution { return new WriteLock(_lock); } - + + /// + /// Returns an upgradeable read lock for use when reading/modifying collections + /// + /// + protected UpgradeableReadLock GetUpgradeableReadLock() + { + return new UpgradeableReadLock(_lock); + } + /// /// Throws an exception if resolution is frozen /// diff --git a/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs index 56ed107e68..977ddee355 100644 --- a/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -42,6 +43,58 @@ namespace Umbraco.Tests.Resolvers Assert.IsTrue(instances1.Select(x => x.GetType()).ContainsAll(new []{typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3)})); } + [Test] + public void Type_List_Delegates_Combination() + { + Func> types = () => new[] { typeof(TransientObject3), typeof(TransientObject2) }; + + var resolver = new LazyResolver(types); + resolver.AddTypeListDelegate(() => new[] { typeof(TransientObject1)}); + + Resolution.Freeze(); + + var instances1 = resolver.Objects; + + Assert.AreEqual(3, instances1.Count()); + Assert.IsTrue(instances1.Select(x => x.GetType()).ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); + } + + [Test] + public void Type_List_Delegates_And_Lazy_Type_Combination() + { + Func> types = () => new[] { typeof(TransientObject3) }; + + var resolver = new LazyResolver(types); + resolver.AddType(new Lazy(() => typeof(TransientObject2))); + resolver.AddType(); + + Resolution.Freeze(); + + var instances1 = resolver.Objects; + + Assert.AreEqual(3, instances1.Count()); + Assert.IsTrue(instances1.Select(x => x.GetType()).ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); + } + + [Test] + public void Throws_If_Duplication() + { + Func> types = () => new[] { typeof(TransientObject3), typeof(TransientObject2), typeof(TransientObject1) }; + + var resolver = new LazyResolver(types); + //duplicate, but will not throw here + resolver.AddType(); + + Resolution.Freeze(); + + Assert.Throws(() => + { + var instances = resolver.Objects; + }); + + + } + #region Test classes private interface ITestInterface @@ -73,6 +126,12 @@ namespace Umbraco.Tests.Resolvers } + public LazyResolver(Func> typeList) + : base(typeList, ObjectLifetimeScope.Transient) + { + + } + public IEnumerable Objects { get { return Values; }