diff --git a/src/Umbraco.Core/Composing/ContainerExtensions.cs b/src/Umbraco.Core/Composing/ContainerExtensions.cs index 33925e010b..86e18db35b 100644 --- a/src/Umbraco.Core/Composing/ContainerExtensions.cs +++ b/src/Umbraco.Core/Composing/ContainerExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -9,10 +10,13 @@ namespace Umbraco.Core.Composing /// public static class ContainerExtensions { + private static readonly ConcurrentDictionary>> ArgumentPropertyGetters + = new ConcurrentDictionary>>(); + /// - /// Gets an instance. + /// Gets an instance of a service. /// - /// The type of the instance. + /// The type of the service. /// The container. /// An instance of the specified type. /// Throws an exception if the container failed to get an instance of the specified type. @@ -20,20 +24,20 @@ namespace Umbraco.Core.Composing => (T) container.GetInstance(typeof(T)); /// - /// Gets a named instance. + /// Gets an instance of a named service. /// - /// The type of the instance. + /// The type of the service. /// The container. - /// The name of the instance. + /// The name of the service. /// An instance of the specified type and name. /// Throws an exception if the container failed to get an instance of the specified type. public static T GetInstance(this IContainer container, string name) => (T) container.GetInstance(typeof(T), name); /// - /// Tries to get an instance. + /// Tries to get an instance of a service. /// - /// The type of the instance. + /// The type of the service. /// An instance of the specified type, or null. /// Returns null if the container does not know how to get an instance /// of the specified type. Throws an exception if the container does know how @@ -42,7 +46,7 @@ namespace Umbraco.Core.Composing => (T) container.TryGetInstance(typeof(T)); /// - /// Gets registration for a service. + /// Gets registrations for a service. /// /// The type of the service. /// The registrations for the service. @@ -63,6 +67,32 @@ namespace Umbraco.Core.Composing public static T CreateInstance(this IContainer container, IDictionary args) => (T) container.CreateInstance(typeof(T), args); + /// + /// Creates an instance with arguments. + /// + /// The type of the instance. + /// The container. + /// Arguments. + /// An instance of the specified type. + /// + /// Throws an exception if the container failed to get an instance of the specified type. + /// The arguments are used as dependencies by the container. + /// + public static T CreateInstance(this IContainer container, object args) + { + var typeOfArgs = args.GetType(); + var getters = ArgumentPropertyGetters.GetOrAdd(typeOfArgs, type => + args.GetType() + .GetProperties() + .ToDictionary(x => x.Name, x => ReflectionUtilities.EmitMethodUnsafe>(x.GetMethod))); + + var argsDictionary = new Dictionary(); + foreach (var (name, getter) in getters) + argsDictionary[name] = getter(args); + + return (T) container.CreateInstance(typeof(T), argsDictionary); + } + /// /// Registers a service with an implementation type. /// @@ -70,15 +100,15 @@ namespace Umbraco.Core.Composing => container.Register(typeof(TService), typeof(TImplementing), lifetime); /// - /// Registers a service with a named implementation type. + /// Registers a named service with an implementation type. /// public static void Register(this IContainer container, string name, Lifetime lifetime = Lifetime.Transient) => container.Register(typeof(TService), typeof(TImplementing), name, lifetime); /// - /// Registers a service with a named implementation type and factory. + /// Registers a named service with an implementation factory. /// - public static void Register(this IContainer container, string name, Func factory, Lifetime lifetime = Lifetime.Transient) + public static void Register(this IContainer container, string name, Func factory, Lifetime lifetime = Lifetime.Transient) => container.Register(factory, name, lifetime); /// @@ -107,7 +137,6 @@ namespace Umbraco.Core.Composing /// /// Registers a named singleton service with an implementation factory. - /// Note to implementors: The last registered component must be the default. /// public static void RegisterSingleton(this IContainer container, string name, Func factory) => container.Register(factory, name, Lifetime.Singleton); @@ -125,7 +154,7 @@ namespace Umbraco.Core.Composing => container.RegisterAuto(typeof(TServiceBase)); /// - /// Registers and instanciates a collection builder. + /// Registers and instantiates a collection builder. /// /// The type of the collection builder. /// A collection builder of the specified type. @@ -137,7 +166,8 @@ namespace Umbraco.Core.Composing throw new InvalidOperationException("Collection builders should be registered only once."); // register the builder - container.RegisterSingleton(); + // use a factory so we don't have to self-register the container + container.RegisterSingleton(factory => factory.CreateInstance(new Dictionary {{ "container", container }} )); // initialize and return the builder return container.GetInstance(); diff --git a/src/Umbraco.Core/Composing/ContainerFactory.cs b/src/Umbraco.Core/Composing/ContainerFactory.cs index fb2c5f6eb7..bf808d56ae 100644 --- a/src/Umbraco.Core/Composing/ContainerFactory.cs +++ b/src/Umbraco.Core/Composing/ContainerFactory.cs @@ -50,6 +50,10 @@ namespace Umbraco.Core.Composing if (container == null) throw new Exception($"Container factory '{configuredTypeName}' did not return an IContainer implementation."); + // self-register the container - this is where it should happen + // but - we do NOT want to do it! + //container.RegisterInstance(container); + return container; } } diff --git a/src/Umbraco.Core/Composing/IContainer.cs b/src/Umbraco.Core/Composing/IContainer.cs index 97a9d47437..78137eb990 100644 --- a/src/Umbraco.Core/Composing/IContainer.cs +++ b/src/Umbraco.Core/Composing/IContainer.cs @@ -3,6 +3,22 @@ using System.Collections.Generic; namespace Umbraco.Core.Composing { + // Implementing IContainer: + // + // The factory + // - always picks the constructor with the most parameters + // - supports Lazy parameters (and prefers them over non-Lazy) in constructors + // - what happens with 'releasing' is unclear + // + // The registry + // - supports registering a service, even after some instances of other services have been created + // - supports re-registering a service, as long as no instance of that service has been created + // - throws when re-registering a service, and an instance of that service has been created + // + // - registers only one implementation of a nameless service, re-registering replaces the previous + // registration - names are required to register multiple implementations - and getting an + // IEnumerable of the service, nameless, returns them all + /// /// Defines a container for Umbraco. /// @@ -15,32 +31,27 @@ namespace Umbraco.Core.Composing #region Factory - // notes - // when implementing IContainer, the following rules apply - // - always pick the constructor with the most parameters - // - always prefer Lazy parameters over non-Lazy in constructors - /// - /// Gets an instance. + /// Gets an instance of a service. /// - /// The type of the instance. + /// The type of the service. /// An instance of the specified type. /// Throws an exception if the container failed to get an instance of the specified type. object GetInstance(Type type); /// - /// Gets a named instance. + /// Gets an instance of a named service. /// - /// The type of the instance. - /// The name of the instance. + /// The type of the service. + /// The name of the service. /// An instance of the specified type. /// Throws an exception if the container failed to get an instance of the specified type. object GetInstance(Type type, string name); /// - /// Tries to get an instance. + /// Tries to get an instance of a service. /// - /// The type of the instance. + /// The type of the service. /// An instance of the specified type, or null. /// Returns null if the container does not know how to get an instance /// of the specified type. Throws an exception if the container does know how @@ -60,14 +71,14 @@ namespace Umbraco.Core.Composing IEnumerable GetAllInstances(); /// - /// Gets registration for a service. + /// Gets registrations for a service. /// /// The type of the service. /// The registrations for the service. IEnumerable GetRegistered(Type serviceType); /// - /// Creates an instance with arguments. + /// Creates an instance of a service, with arguments. /// /// The type of the instance. /// Named arguments. @@ -79,16 +90,21 @@ namespace Umbraco.Core.Composing /// object CreateInstance(Type type, IDictionary args); + /// + /// Releases an instance. + /// + /// The instance. + /// + /// See https://stackoverflow.com/questions/14072208 and http://kozmic.net/2010/08/27/must-i-release-everything-when-using-windsor/, + /// you only need to release instances you specifically resolved, and even then, if done right, that might never be needed. For + /// instance, LightInject does not require this and does not support it - should work with scopes. + /// + void Release(object instance); + #endregion #region Registry - // notes - // when implementing IContainer, the following rules apply - // - it is possible to register a service, even after some instances of other services have been created - // - it is possible to re-register a service, as long as an instance of that service has not been created - // - once an instance of a service has been created, it is not possible to change its registration - /// /// Registers a service as its own implementation. /// @@ -100,7 +116,7 @@ namespace Umbraco.Core.Composing void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient); /// - /// Registers a service with a named implementation type. + /// Registers a named service with a implementation type. /// void Register(Type serviceType, Type implementingType, string name, Lifetime lifetime = Lifetime.Transient); @@ -120,7 +136,7 @@ namespace Umbraco.Core.Composing void RegisterInstance(Type serviceType, object instance); /// - /// Registers a service with a named implementing instance. + /// Registers a named service with an implementing instance. /// void RegisterInstance(Type serviceType, object instance, string name); @@ -139,6 +155,7 @@ namespace Umbraco.Core.Composing /// /// Registers a service with an ordered set of implementation types. /// + // fixme: once we merge the installer refactoring, kill that one void RegisterOrdered(Type serviceType, Type[] implementingTypes, Lifetime lifetime = Lifetime.Transient); #endregion @@ -174,7 +191,5 @@ namespace Umbraco.Core.Composing IContainer EnablePerWebRequestScope(); #endregion - - void Release(object instance); } } diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs index 1302676514..dca07938cc 100644 --- a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs @@ -20,8 +20,6 @@ namespace Umbraco.Core.Composing.LightInject protected LightInjectContainer(ServiceContainer container) { Container = container; - - container.RegisterSingleton(_ => this); } /// @@ -154,6 +152,12 @@ namespace Umbraco.Core.Composing.LightInject return ctor.Invoke(ctorArgs); } + /// + public void Release(object instance) + { + // nothing to release with LightInject + } + // notes: // we may want to look into MS code, eg: // TypeActivatorCache in MVC at https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs @@ -318,11 +322,6 @@ namespace Umbraco.Core.Composing.LightInject #endregion - public void Release(object instance) - { - // fixme - no idea how to do this with LI - } - #region Control /// diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 46dae8bcfd..b87ac70ea5 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using NUnit.Framework; using Umbraco.Core; @@ -553,6 +554,22 @@ namespace Umbraco.Tests.Clr // fixme - missing tests specifying 'returned' on method, property + [Test] + public void DeconstructAnonymousType() + { + var o = new { a = 1, b = "hello" }; + + var getters = new Dictionary>(); + foreach (var prop in o.GetType().GetProperties()) + getters[prop.Name] = ReflectionUtilities.EmitMethodUnsafe>(prop.GetMethod); + + Assert.AreEqual(2, getters.Count); + Assert.IsTrue(getters.ContainsKey("a")); + Assert.IsTrue(getters.ContainsKey("b")); + Assert.AreEqual(1, getters["a"](o)); + Assert.AreEqual("hello", getters["b"](o)); + } + #region IL Code // these functions can be examined in eg DotPeek to understand IL works