diff --git a/src/Umbraco.Core/Current.cs b/src/Umbraco.Core/Current.cs
index bc588fc986..568a35578e 100644
--- a/src/Umbraco.Core/Current.cs
+++ b/src/Umbraco.Core/Current.cs
@@ -1,10 +1,12 @@
-using LightInject;
+using System.Collections.Generic;
+using LightInject;
+using Umbraco.Core.Strings;
namespace Umbraco.Core
{
// must remain internal - this class is here to support the transition from singletons
// and resolvers to injection - by providing a static access to singleton services - it
- // is initialized once with a service container, in WebBootManager.
+ // is initialized once with a service container, in CoreBootManager.
internal class Current
{
public static IServiceContainer Container { get; set; } // ok to set - don't be stupid
diff --git a/src/Umbraco.Core/DependencyInjection/IInjectCollection.cs b/src/Umbraco.Core/DependencyInjection/IInjectCollection.cs
new file mode 100644
index 0000000000..c9d90ea027
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/IInjectCollection.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Represents an injected collection, ie an immutable enumeration of items.
+ ///
+ /// The type of the items.
+ public interface IInjectCollection
+ {
+ ///
+ /// Gets the items in the collection.
+ ///
+ IEnumerable Items { get; }
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/IInjectCollectionBuilder.cs b/src/Umbraco.Core/DependencyInjection/IInjectCollectionBuilder.cs
new file mode 100644
index 0000000000..1f6ea68f0d
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/IInjectCollectionBuilder.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Represents an injected collection builder.
+ ///
+ /// The type of the collection.
+ /// The type of the items.
+ public interface IInjectCollectionBuilder
+ where TCollection : IInjectCollection
+ {
+ ///
+ /// Gets a collection.
+ ///
+ /// A collection.
+ /// The lifetime of the collection depends on how the builder was registered.
+ TCollection GetCollection();
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/InjectCollectionBase.cs b/src/Umbraco.Core/DependencyInjection/InjectCollectionBase.cs
new file mode 100644
index 0000000000..d088c4461c
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/InjectCollectionBase.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Provides a base class for injected collections.
+ ///
+ /// The type of the items.
+ public abstract class InjectCollectionBase : IInjectCollection
+ {
+ ///
+ /// Initializes a new instance of the with items.
+ ///
+ /// The items.
+ protected InjectCollectionBase(IEnumerable items)
+ {
+ Items = items;
+ }
+
+ ///
+ public IEnumerable Items { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/InjectCollectionBuilderBase.cs b/src/Umbraco.Core/DependencyInjection/InjectCollectionBuilderBase.cs
new file mode 100644
index 0000000000..f0d65eeb34
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/InjectCollectionBuilderBase.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LightInject;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Provides a base class for injected collection builders.
+ ///
+ /// The type of the collection.
+ /// The type of the items.
+ public abstract class InjectCollectionBuilderBase : IInjectCollectionBuilder
+ where TCollection : IInjectCollection
+ {
+ private readonly IServiceContainer _container;
+ private readonly List _types = new List();
+ private readonly object _locker = new object();
+ private bool _typesRegistered;
+
+ ///
+ /// Initializes a new instance of the
+ /// class with a service container.
+ ///
+ /// A service container.
+ protected InjectCollectionBuilderBase(IServiceContainer container)
+ {
+ _container = container;
+ }
+
+ // everything could be implemented, add, insert, remove, replace, order, whatever...
+ // and we could also have 'lazy' collections by supplying a types factory
+
+ protected void Configure(Action action)
+ {
+ lock (_locker)
+ {
+ if (_typesRegistered) throw new InvalidOperationException();
+ action();
+ }
+ }
+
+ public void Add()
+ where T : TItem
+ {
+ Configure(() =>
+ {
+ var type = typeof(T);
+ if (!_types.Contains(type))
+ _types.Add(type);
+ });
+ }
+
+ protected virtual IEnumerable PrepareTypes(IEnumerable types)
+ {
+ return types;
+ }
+
+ private void RegisterTypes()
+ {
+ lock (_locker)
+ {
+ if (_typesRegistered) return;
+
+ var prefix = GetType().FullName + "_";
+ var i = 0;
+ foreach (var type in PrepareTypes(_types))
+ {
+ var name = $"{prefix}{i++:00000}";
+ _container.Register(typeof(TItem), type, name);
+ }
+
+ _typesRegistered = true;
+ }
+ }
+
+ ///
+ /// Gets a collection.
+ ///
+ /// A collection.
+ ///
+ /// Creates a new collection each time it is invoked, but only registers types once.
+ /// Explicitely implemented in order to hide it to users.
+ ///
+ TCollection IInjectCollectionBuilder.GetCollection()
+ {
+ RegisterTypes(); // will do it only once
+
+ var prefix = GetType().FullName + "_";
+
+ // fixme - shouldn't we save this somewhere?
+ var services = _container.AvailableServices
+ .Where(x => x.ServiceName.StartsWith(prefix))
+ .OrderBy(x => x.ServiceName);
+
+ var items = services
+ .Select(x => _container.GetInstance(x.ServiceName))
+ .ToArray();
+
+ return CreateCollection(items);
+ }
+
+ ///
+ /// Creates the injected collection.
+ ///
+ /// The items.
+ /// An injected collection containing the items.
+ protected abstract TCollection CreateCollection(IEnumerable items);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/InjectLazyCollectionBuilderBase.cs b/src/Umbraco.Core/DependencyInjection/InjectLazyCollectionBuilderBase.cs
new file mode 100644
index 0000000000..722abaa58f
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/InjectLazyCollectionBuilderBase.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LightInject;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ public abstract class InjectLazyCollectionBuilderBase : InjectCollectionBuilderBase
+ where TCollection : IInjectCollection
+ {
+ private readonly List>> _producers = new List>>();
+ private readonly List _excluded = new List();
+
+ protected InjectLazyCollectionBuilderBase(IServiceContainer container)
+ : base(container)
+ { }
+
+ protected override IEnumerable PrepareTypes(IEnumerable types)
+ {
+ return types.Union(_producers.SelectMany(x => x())).Distinct().Except(_excluded);
+ }
+
+ public void AddProducer(Func> producer)
+ {
+ Configure(() =>
+ {
+ _producers.Add(producer);
+ });
+ }
+
+ public void Exclude()
+ {
+ Configure(() =>
+ {
+ var type = typeof(T);
+ if (!_excluded.Contains(type))
+ _excluded.Add(type);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/InjectWeightedCollectionBuilderBase.cs b/src/Umbraco.Core/DependencyInjection/InjectWeightedCollectionBuilderBase.cs
new file mode 100644
index 0000000000..be164ffcc0
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/InjectWeightedCollectionBuilderBase.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LightInject;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ public abstract class InjectWeightedCollectionBuilderBase : InjectCollectionBuilderBase
+ where TCollection : IInjectCollection
+ {
+ protected InjectWeightedCollectionBuilderBase(IServiceContainer container)
+ : base(container)
+ { }
+
+ protected override IEnumerable PrepareTypes(IEnumerable types)
+ {
+ var list = types.ToList();
+ list.Sort((t1, t2) => GetWeight(t1).CompareTo(GetWeight(t2)));
+ return list;
+ }
+
+ protected virtual int DefaultWeight { get; set; } = 10;
+
+ protected virtual int GetWeight(Type type)
+ {
+ var attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType().SingleOrDefault();
+ return attr?.Weight ?? DefaultWeight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs b/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs
index e791db2ac8..ea112bbdb0 100644
--- a/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs
+++ b/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs
@@ -162,6 +162,36 @@ namespace Umbraco.Core.DependencyInjection
}
}
-
+ ///
+ /// Registers an injected collection.
+ ///
+ /// The type of the collection builder.
+ /// The type of the collection.
+ /// The type of the items.
+ /// A container.
+ public static void RegisterCollection(this IServiceRegistry container)
+ where TCollection : IInjectCollection
+ where TBuilder : IInjectCollectionBuilder
+ {
+ container.Register(new PerContainerLifetime());
+ container.Register(factory => factory.GetInstance().GetCollection());
+ }
+
+ ///
+ /// Registers an injected collection.
+ ///
+ /// The type of the collection builder.
+ /// The type of the collection.
+ /// The type of the items.
+ /// A lifetime type.
+ /// A container.
+ public static void RegisterCollection(this IServiceRegistry container)
+ where TCollection : IInjectCollection
+ where TBuilder : IInjectCollectionBuilder
+ where TLifetime : ILifetime, new()
+ {
+ container.Register(new PerContainerLifetime());
+ container.Register(factory => factory.GetInstance().GetCollection(), new TLifetime());
+ }
}
}
diff --git a/src/Umbraco.Core/DependencyInjection/WeightAttribute.cs b/src/Umbraco.Core/DependencyInjection/WeightAttribute.cs
new file mode 100644
index 0000000000..3dfbc5b5a7
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/WeightAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Specifies the weight of pretty much anything.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ public class WeightAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class with a weight.
+ ///
+ ///
+ public WeightAttribute(int weight)
+ {
+ Weight = weight;
+ }
+
+ ///
+ /// Gets the weight value.
+ ///
+ public int Weight { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index b01dd2e1f2..6f4ec68181 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -215,8 +215,15 @@
+
+
+
+
+
+
+
@@ -1328,7 +1335,6 @@
-
diff --git a/src/Umbraco.Web/Current.cs b/src/Umbraco.Web/Current.cs
index 265cf0b7a2..c655f9fb57 100644
--- a/src/Umbraco.Web/Current.cs
+++ b/src/Umbraco.Web/Current.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using LightInject;
using Umbraco.Core.Events;
+using Umbraco.Core.Strings;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web
@@ -32,7 +34,6 @@ namespace Umbraco.Web
set { _umbracoContextAccessor = value; } // for tests
}
-
public static IFacadeAccessor FacadeAccessor
{
get
@@ -44,7 +45,6 @@ namespace Umbraco.Web
set { _facadeAccessor = value; } // for tests
}
-
public static UmbracoContext UmbracoContext => UmbracoContextAccessor.UmbracoContext;
// have to support set for now, because of 'ensure umbraco context' which can create