diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index ecbd9533ee..efdef82114 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -63,8 +63,7 @@ namespace Umbraco.Core.Composing // else _collectionCtor remains null, assuming CreateCollection has been overriden // we just don't want to support re-registering collections here - var registration = Container.GetRegistered().FirstOrDefault(); - if (registration != null) + if (Container.GetRegistered().Any()) throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered."); // register the collection diff --git a/src/Umbraco.Core/Composing/ContainerExtensions.cs b/src/Umbraco.Core/Composing/ContainerExtensions.cs index 8fb8208c4a..e51fc07aa4 100644 --- a/src/Umbraco.Core/Composing/ContainerExtensions.cs +++ b/src/Umbraco.Core/Composing/ContainerExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Umbraco.Core.Composing { @@ -71,5 +72,26 @@ namespace Umbraco.Core.Composing public static void RegisterAuto(this IContainer container) => container.RegisterAuto(typeof(TServiceBase)); + + /// + /// Registers and instanciates a collection builder. + /// + /// The type of the collection builder. + /// A collection builder of the specified type. + public static TBuilder RegisterCollectionBuilder(this IContainer container) + { + // make sure it's not already registered + // we just don't want to support re-registering collection builders + if (container.GetRegistered().Any()) + throw new InvalidOperationException("Collection builders should be registered only once."); + + // register the builder - per container + container.RegisterSingleton(); + + // initialize and return the builder + // note: see notes in IContainer - cannot pass the container as a parameter to a singleton + // and so, for now, the container is registered into itself + return container.GetInstance(/*new object[] { container }*/); + } } } diff --git a/src/Umbraco.Core/Composing/IContainer.cs b/src/Umbraco.Core/Composing/IContainer.cs index 6e0417a334..9d77a9b379 100644 --- a/src/Umbraco.Core/Composing/IContainer.cs +++ b/src/Umbraco.Core/Composing/IContainer.cs @@ -114,14 +114,8 @@ namespace Umbraco.Core.Composing /// void RegisterOrdered(Type serviceType, Type[] implementingTypes, Lifetime lifetime = Lifetime.Transient); - /// - /// Registers and instanciates a collection builder. - /// - /// The type of the collection builder. - /// A collection builder of the specified type. - T RegisterCollectionBuilder(); - // fixme - very LightInject specific? or? + // beware! does NOT work on singletons, see https://github.com/seesharper/LightInject/issues/294 void RegisterConstructorDependency(Func factory); void RegisterConstructorDependency(Func factory); diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs index de942c1e4c..8d8197f10c 100644 --- a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using System.Linq; +using System.Threading; using LightInject; namespace Umbraco.Core.Composing.LightInject @@ -11,14 +12,28 @@ namespace Umbraco.Core.Composing.LightInject /// public class LightInjectContainer : IContainer { + private int _disposed; + /// /// Initializes a new instance of the with a LightInject container. /// - public LightInjectContainer(ServiceContainer container) + protected LightInjectContainer(ServiceContainer container) { Container = container; } + /// + /// Creates a new instance of the class. + /// + public static LightInjectContainer Create() + => new LightInjectContainer(CreateServiceContainer()); + + /// + /// Creates a new instance of the LightInject service container. + /// + protected static ServiceContainer CreateServiceContainer() + => new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false }); + /// /// Gets the LightInject container. /// @@ -29,7 +44,12 @@ namespace Umbraco.Core.Composing.LightInject /// public void Dispose() - => Container.Dispose(); + { + if (Interlocked.Exchange(ref _disposed, 1) == 1) + return; + + Container.Dispose(); + } #region Factory @@ -186,10 +206,6 @@ namespace Umbraco.Core.Composing.LightInject public void RegisterConstructorDependency(Func factory) => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a)); - /// - public T RegisterCollectionBuilder() - => Container.RegisterCollectionBuilder(); - #endregion #region Control @@ -205,6 +221,9 @@ namespace Umbraco.Core.Composing.LightInject // eg to specify the service name on some services Container.EnableAnnotatedConstructorInjection(); + // note: the block below is disabled, we do not allow property injection at all anymore + // (see options in CreateServiceContainer) + // // from the docs: "LightInject considers all read/write properties a dependency, but implements // a loose strategy around property dependencies, meaning that it will NOT throw an exception // in the case of an unresolved property dependency." @@ -216,7 +235,7 @@ namespace Umbraco.Core.Composing.LightInject // could not find it documented, but tests & code review shows that LightInject considers a // property to be "injectable" when its setter exists and is not static, nor private, nor // it is an index property. which means that eg protected or internal setters are OK. - Container.EnableAnnotatedPropertyInjection(); + //Container.EnableAnnotatedPropertyInjection(); // ensure that we do *not* scan assemblies // we explicitely RegisterFrom our own composition roots and don't want them scanned @@ -225,6 +244,17 @@ namespace Umbraco.Core.Composing.LightInject // see notes in MixedLightInjectScopeManagerProvider Container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider(); + // note: the block below is disabled, because it does not work, because collection builders + // are singletons, and constructor dependencies don't work on singletons, see + // https://github.com/seesharper/LightInject/issues/294 + // + // if looking for a IContainer, and one was passed in args, use it + // this is for collection builders which require the IContainer + //Container.RegisterConstructorDependency((c, i, a) => a.OfType().FirstOrDefault()); + + // which means that the only way to inject the container into builders is to register it + Container.RegisterInstance(this); + return this; } diff --git a/src/Umbraco.Core/Composing/LightInjectExtensions.cs b/src/Umbraco.Core/Composing/LightInjectExtensions.cs index 6c8961ec66..30a4c0efb5 100644 --- a/src/Umbraco.Core/Composing/LightInjectExtensions.cs +++ b/src/Umbraco.Core/Composing/LightInjectExtensions.cs @@ -206,28 +206,5 @@ namespace Umbraco.Core.Composing var typeofTService = typeof(TService); return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); } - - /// - /// Registers and instanciates a collection builder. - /// - /// The type of the collection builder. - /// The container. - /// The collection builder. - public static TBuilder RegisterCollectionBuilder(this IServiceContainer container) - { - // make sure it's not already registered - // we just don't want to support re-registering collection builders - var registration = container.GetAvailableService(); - if (registration != null) - throw new InvalidOperationException("Collection builders should be registered only once."); - - // register the builder - per container - var builderLifetime = new PerContainerLifetime(); - container.Register(builderLifetime); - - // return the builder - // (also initializes the builder) - return container.GetInstance(); - } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 6320401681..d06b6215b3 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Configuration; using AutoMapper; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index 98ddbc883f..20b2131217 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core { // note: the actual, web UmbracoApplication is overriding this // with a web-supporting container - return new Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + return Composing.LightInject.LightInjectContainer.Create(); } // events - in the order they trigger diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index d8f3e615ec..750768eb1d 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Cache.DistributedCache [SetUp] public void Setup() { - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); container.Register(_ => new TestServerRegistrar()); diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index d34df9b8b2..6516d2fbfd 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Composing { Current.Reset(); - _container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + _container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); _container.ConfigureForUmbraco(); } diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 97dde9e43a..51f38cebf0 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -23,7 +23,7 @@ namespace Umbraco.Tests.Composing } private IContainer CreateContainer() - => Current.Container = (new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer())).ConfigureForUmbraco(); + => Current.Container = Core.Composing.LightInject.LightInjectContainer.Create().ConfigureForUmbraco(); // note // lazy collection builder does not throw on duplicate, just uses distinct types diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index 0b318f80cf..01fa9e7835 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests.Composing [Test] public void PackageActionCollectionBuilderWorks() { - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); container.RegisterCollectionBuilder() diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 75a41560cf..eb437d6a51 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.IO var config = SettingsForTests.GetDefaultUmbracoSettings(); SettingsForTests.ConfigureSettings(config); - _container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + _container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); _container.ConfigureForUmbraco(); _container.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 3576e3aa60..4c73ed8409 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -65,7 +65,7 @@ namespace Umbraco.Tests.PropertyEditors { try { - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); container.RegisterCollectionBuilder(); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index c7924f12a6..38a37f0083 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.PropertyEditors //normalize culture Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); container.Register(_ diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 112dda9c17..44e84e98fb 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -171,7 +171,7 @@ namespace Umbraco.Tests.Published public void SimpleConverter3Test() { Current.Reset(); - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 0505072e5c..89d361e789 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.Scoping DoThing2 = null; DoThing3 = null; - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); _testObjects = new TestObjects(container); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 1078a49299..1549de8554 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.TestHelpers var sqlSyntax = new SqlCeSyntaxProvider(); - var container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + var container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); container.ConfigureForUmbraco(); container.RegisterSingleton(factory => Mock.Of()); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index da96c40299..19e5d12650 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -143,12 +143,17 @@ namespace Umbraco.Tests.TestHelpers public IFileSystems GetFileSystemsMock() { var fileSystems = Mock.Of(); + MockFs(fileSystems, x => x.MasterPagesFileSystem); MockFs(fileSystems, x => x.MacroPartialsFileSystem); MockFs(fileSystems, x => x.MvcViewsFileSystem); MockFs(fileSystems, x => x.PartialViewsFileSystem); MockFs(fileSystems, x => x.ScriptsFileSystem); MockFs(fileSystems, x => x.StylesheetsFileSystem); + + var mediaFs = new MediaFileSystem(Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); + Mock.Get(fileSystems).Setup(x => x.MediaFileSystem).Returns(mediaFs); + return fileSystems; } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index f3029b3a35..04b22831a4 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -113,7 +113,7 @@ namespace Umbraco.Tests.Testing // but hey, never know, better avoid garbage-in Reset(); - Container = Current.Container = new Core.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + Container = Current.Container = Core.Composing.LightInject.LightInjectContainer.Create(); Container.ConfigureForUmbraco(); TestObjects = new TestObjects(Container); @@ -286,6 +286,10 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(factory => ExamineManager.Instance); + // register filesystems + Container.RegisterSingleton(factory => TestObjects.GetFileSystemsMock()); + Container.RegisterSingleton(factory => factory.GetInstance().MediaFileSystem); + // no factory (noop) Container.RegisterSingleton(); @@ -304,8 +308,7 @@ namespace Umbraco.Tests.Testing Container.RegisterSingleton(f => f.TryGetInstance().SqlContext); Container.RegisterCollectionBuilder(); // empty - Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); - Container.RegisterSingleton(factory => factory.GetInstance()); + Container.RegisterSingleton(factory => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); Container.RegisterSingleton(factory => (IScopeAccessor) factory.GetInstance()); diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index f6d8053305..cf1a419769 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -1,18 +1,15 @@ using System; using System.Threading; using System.Web; -using LightInject; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Macros; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Composing; -using Umbraco.Core.Migrations; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; @@ -22,7 +19,6 @@ using Umbraco.Core._Legacy.PackageActions; using Umbraco.Web.Cache; using Umbraco.Web.Editors; using Umbraco.Web.HealthCheck; -using Umbraco.Web.Media; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Web/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Web/Composing/LightInject/LightInjectContainer.cs index cf6a949fac..0237e2a641 100644 --- a/src/Umbraco.Web/Composing/LightInject/LightInjectContainer.cs +++ b/src/Umbraco.Web/Composing/LightInject/LightInjectContainer.cs @@ -14,10 +14,16 @@ namespace Umbraco.Web.Composing.LightInject /// /// Initializes a new instance of the with a LightInject container. /// - public LightInjectContainer(ServiceContainer container) + protected LightInjectContainer(ServiceContainer container) : base(container) { } + /// + /// Creates a new instance of the class. + /// + public new static LightInjectContainer Create() + => new LightInjectContainer(CreateServiceContainer()); + /// public override IContainer ConfigureForWeb() { diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 51c356558f..d9578d4877 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; using AutoMapper; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; -using LightInject; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; @@ -19,7 +16,6 @@ namespace Umbraco.Web.Editors { public IEnumerable
GetSections() { - var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0)); var sectionModels = sections.Select(Mapper.Map).ToArray(); @@ -29,11 +25,8 @@ namespace Umbraco.Web.Editors var dashboardHelper = new DashboardHelper(Services.SectionService); // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that - // since tree's by nature are controllers and require request contextual data - and then we have to - // remember to inject properties - nasty indeed - var appTreeController = new ApplicationTreeController(); - ((IServiceContainer)Current.Container.ConcreteContainer).InjectProperties(appTreeController); - appTreeController.ControllerContext = ControllerContext; + // since tree's by nature are controllers and require request contextual data + var appTreeController = new ApplicationTreeController { ControllerContext = ControllerContext }; var dashboards = dashboardHelper.GetDashboards(Security.CurrentUser); //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index bd790cdfb2..40ebac4536 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web protected override IContainer GetContainer() { - return new Web.Composing.LightInject.LightInjectContainer(new LightInject.ServiceContainer()); + return Composing.LightInject.LightInjectContainer.Create(); } } } diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs index 849fb3b619..afd6c2c1af 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs @@ -1,16 +1,24 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using Umbraco.Core; using Umbraco.Core.Composing; namespace Umbraco.Web._Legacy.Actions { - public class ActionCollection : BuilderCollectionBase + public class ActionCollection : IBuilderCollection { - public ActionCollection(IEnumerable items) - : base(items) - { } + private Func> _producer; + private readonly object _locker = new object(); + private IAction[] _items; + + internal ActionCollection(Func> producer) + { + _producer = producer; + } internal T GetAction() where T : IAction @@ -25,5 +33,50 @@ namespace Umbraco.Web._Legacy.Actions .WhereNotNull() .ToArray(); } + + private IAction[] Items + { + get + { + lock (_locker) + { + if (_items != null) return _items; + + var actions = new List(); + foreach (var type in _producer()) + { + var getter = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); + var instance = getter == null + ? Activator.CreateInstance(type) as IAction + : getter.GetValue(null, null) as IAction; + if (instance == null) continue; + actions.Add(instance); + } + + return _items = actions.ToArray(); + } + } + } + + internal void Reset(Func> producer) + { + lock (_locker) + { + _items = null; + _producer = producer; + } + } + + public int Count => Items.Length; + + public IEnumerator GetEnumerator() + { + return ((IEnumerable) Items).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } } diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs index 39058d6836..1789e57e2d 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs @@ -1,71 +1,34 @@ using System; using System.Collections.Generic; -using System.Reflection; -using LightInject; using Umbraco.Core.Composing; namespace Umbraco.Web._Legacy.Actions { internal class ActionCollectionBuilder : ICollectionBuilder { - private static Func> _producer; + private Func> _producer; + private ActionCollection _collection; // for tests only - does not register the collection public ActionCollectionBuilder() { } - public ActionCollectionBuilder(IServiceContainer container) + public ActionCollectionBuilder(IContainer container) { - var collectionLifetime = CollectionLifetime; - - // register the collection - special lifetime - // the lifetime here is custom ResettablePerContainerLifetime which will manage one - // single instance of the collection (much alike PerContainerLifetime) but can be resetted - // to force a new collection to be created. - // this is needed because of the weird things we do during install, where we'd use the - // infamous DirtyBackdoorToConfiguration to reset the ActionResolver way after Resolution - // had frozen. This has been replaced by the possibility here to set the producer at any - // time - but the builder is internal - and all this will be gone eventually. - container.Register(factory => factory.GetInstance().CreateCollection(), collectionLifetime); + // register the collection + container.RegisterSingleton(factory => factory.GetInstance().CreateCollection()); } public ActionCollection CreateCollection() { - var actions = new List(); - foreach (var type in _producer()) - { - var getter = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); - var instance = getter == null - ? Activator.CreateInstance(type) as IAction - : getter.GetValue(null, null) as IAction; - if (instance == null) continue; - actions.Add(instance); - } - return new ActionCollection(actions); + // create a special collection that can be resetted (ouch) + return _collection = new ActionCollection(_producer); } public void SetProducer(Func> producer) { _producer = producer; - CollectionLifetime.Reset(); - } - - private ResettablePerContainerLifetime CollectionLifetime { get; } = new ResettablePerContainerLifetime(); - - private class ResettablePerContainerLifetime : ILifetime - { - private object _instance; - - public object GetInstance(Func createInstance, Scope scope) - { - // not dealing with disposable instances, actions are not disposable - return _instance ?? (_instance = createInstance()); - } - - public void Reset() - { - _instance = null; - } + _collection?.Reset(producer); } } }