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