IoC-Issue-161 (lazy registration)

9 views
Skip to first unread message

Krzysztof Koźmic

unread,
Oct 30, 2009, 4:18:07 PM10/30/09
to castle-pro...@googlegroups.com
Hey,

I took a shot today at implementing the feature we were discussing few
weeks back. I didn't commit it, as I wanted someone to review it first,
and testdrive it in real life scenario, which is what I'm spiking right now.
Anyway, please look at the patch, tell me what you think, what can be
done better, what scenarios I didn't think of etc.

Krzysztof

ioc-161.patch

Mauricio Scheffer

unread,
Nov 1, 2009, 5:56:08 PM11/1/09
to Castle Project Development List
Here's the original discussion for reference:

http://groups.google.com/group/castle-project-devel/browse_thread/thread/9726bd1ad6a1e78

On Oct 30, 5:18 pm, Krzysztof Koźmic <krzysztof.koz...@gmail.com>
wrote:

> [ioc-161.patch23K ]Index: src/Castle.MicroKernel.Tests/Castle.MicroKernel.Tests-vs2008.csproj
> ===================================================================
> --- src/Castle.MicroKernel.Tests/Castle.MicroKernel.Tests-vs2008.csproj (revision 6294)
> +++ src/Castle.MicroKernel.Tests/Castle.MicroKernel.Tests-vs2008.csproj (working copy)
> @@ -229,6 +229,7 @@
>      <Compile Include="GraphTestCase.cs">
>        <SubType>Code</SubType>
>      </Compile>
> +    <Compile Include="LazyLoadingTestCase.cs" />
>      <Compile Include="Lifecycle\Components\HttpFakeServer.cs">
>        <SubType>Code</SubType>
>      </Compile>
> Index: src/Castle.MicroKernel.Tests/LazyLoadingTestCase.cs
> ===================================================================
> --- src/Castle.MicroKernel.Tests/LazyLoadingTestCase.cs (revision 0)
> +++ src/Castle.MicroKernel.Tests/LazyLoadingTestCase.cs (revision 0)
> @@ -0,0 +1,125 @@
> +namespace Castle.MicroKernel.Tests
> +{
> +       using System;
> +
> +       using Castle.Core;
> +       using Castle.MicroKernel.Registration;
> +       using Castle.MicroKernel.SubSystems.Naming;
> +
> +       using NUnit.Framework;
> +
> +       [TestFixture]
> +       public class LazyLoadingTestCase
> +       {
> +               private IKernel kernel;
> +
> +               [SetUp]
> +               public void SetUp()
> +               {
> +                       kernel = new DefaultKernel();
> +                       kernel.AddComponent<Loader>(typeof(ILazyComponentLoader));
> +               }
> +
> +               [Test]
> +               public void Can_Lazily_resolve_component()
> +               {
> +
> +                       var service = kernel.Resolve("foo", typeof(IHasDefaultImplementation));
> +                       Assert.IsNotNull(service);
> +                       Assert.IsInstanceOf<Implementation>(service);
> +               }
> +
> +               [Test]
> +               public void Can_lazily_resolve_dependency()
> +               {
> +                       kernel.AddComponent<UsingLazyComponent>();
> +                       var component = kernel.Resolve<UsingLazyComponent>();
> +                       Assert.IsNotNull(component.Dependency);
> +               }
> +
> +               [Test]
> +               [Ignore("Not sure how to do this in an elegant way... plus I don't think it's the right place for this anyway.")]
> +               public void Can_lazily_resolve_parameters()
> +               {
> +                       kernel.AddComponent<UsingString>();
> +                       var component = kernel.Resolve<UsingString>();
> +                       Assert.AreEqual("Foo", component.Parameter);
> +               }
> +       }
> +
> +       public class Loader : ILazyComponentLoader
> +       {
> +               public bool CanLoad(string key, Type service)
> +               {
> +                       return Attribute.IsDefined(service, typeof(DefaultImplementationAttribute));
> +               }
> +
> +               public IRegistration Load(string key, Type service)
> +               {
> +                       var attributes = service.GetCustomAttributes(typeof(DefaultImplementationAttribute), false);
> +                       var attribute = attributes[0] as DefaultImplementationAttribute;
> +                       return Component.For(service).ImplementedBy(attribute.Implementation).Named(key);
> +               }
> +       }
> +
> +       public class UsingString
> +       {
> +               private readonly string parameter;
> +
> +               public UsingString(string parameter)
> +               {
> +                       this.parameter = parameter;
> +               }
> +
> +               public string Parameter
> +               {
> +                       get { return parameter; }
> +               }
> +       }
> +
> +
> +       public class UsingLazyComponent
> +       {
> +               private IHasDefaultImplementation dependency;
> +
> +               public UsingLazyComponent(IHasDefaultImplementation dependency)
> +               {
> +                       this.dependency = dependency;
> +               }
> +
> +               public IHasDefaultImplementation Dependency
> +               {
> +                       get { return dependency; }
> +               }
> +       }
> +
> +       [DefaultImplementation(typeof(Implementation))]
> +       public interface IHasDefaultImplementation
> +       {
> +               void Foo();
> +       }
> +
> +       public class Implementation : IHasDefaultImplementation
> +       {
> +               public void Foo()
> +               {
> +                      
> +               }
> +       }
> +
> +       [AttributeUsage(AttributeTargets.Interface,AllowMultiple = false)]
> +       public class DefaultImplementationAttribute:Attribute
> +       {
> +               private readonly Type implementation;
> +
> +               public DefaultImplementationAttribute(Type implementation)
> +               {
> +                       this.implementation = implementation;
> +               }
> +
> +               public Type Implementation
> +               {
> +                       get { return implementation; }
> +               }
> +       }
> +}
> \ No newline at end of file
> Index: src/Castle.MicroKernel/Castle.MicroKernel-vs2008.csproj
> ===================================================================
> --- src/Castle.MicroKernel/Castle.MicroKernel-vs2008.csproj     (revision 6294)
> +++ src/Castle.MicroKernel/Castle.MicroKernel-vs2008.csproj     (working copy)
> @@ -461,6 +461,7 @@
>        <SubType>Code</SubType>
>      </Compile>
>      <Compile Include="SubSystems\Naming\IHandlerSelector.cs" />
> +    <Compile Include="SubSystems\Naming\ILazyComponentLoader.cs" />
>      <Compile Include="SubSystems\Naming\KeySearchNamingSubSystem.cs" />
>      <Compile Include="SubSystems\Naming\INamingSubSystem.cs">
>        <SubType>Code</SubType>
> Index: src/Castle.MicroKernel/DefaultKernel.cs
> ===================================================================
> --- src/Castle.MicroKernel/DefaultKernel.cs     (revision 6294)
> +++ src/Castle.MicroKernel/DefaultKernel.cs     (working copy)
> @@ -296,6 +296,16 @@
>                         return false;
>                 }
>
> +               public virtual bool LazyLoadComponent(string key, Type service)
> +               {
> +                       if(key == null && service == null)
> +                       {
> +                               throw new ArgumentException("At least one - key or service must not be a null reference.");
> +                       }
> +
> +                       return NamingSubSystem.LoadComponent(key, service);
> +               }
> +
>                 /// <summary>
>                 /// Associates objects with a component handler,
>                 /// allowing it to use the specified dictionary
> @@ -727,7 +737,7 @@
>                 /// <param name="serviceType">An object that specifies the type of service object to get. </param>
>                 public object GetService(Type serviceType)
>                 {
> -                       if (!HasComponent(serviceType))
> +                       if (!HasComponent(serviceType) && !LazyLoadComponent(null, serviceType))
>                         {
>                                 return null;
>                         }
> @@ -744,14 +754,7 @@
>                 /// </returns>
>                 public T GetService<T>() where T : class
>                 {
> -                       Type serviceType = typeof(T);
> -
> -                       if (!HasComponent(serviceType))
> -                       {
> -                               return null;
> -                       }
> -
> -                       return (T)Resolve(serviceType);
> +                       return (T)GetService(typeof(T));
>                 }
>
>                 #endregion
> Index: src/Castle.MicroKernel/DefaultKernel_Resolve.cs
> ===================================================================
> --- src/Castle.MicroKernel/DefaultKernel_Resolve.cs     (revision 6294)
> +++ src/Castle.MicroKernel/DefaultKernel_Resolve.cs     (working copy)
> @@ -91,7 +91,7 @@
>                         {
>                                 if (key == null) throw new ArgumentNullException("key");
>
> -                               if (!HasComponent(key))
> +                               if (!HasComponent(key) && !LazyLoadComponent(key, null))
>                                 {
>                                         throw new ComponentNotFoundException(key);
>                                 }
> @@ -108,7 +108,7 @@
>                         {
>                                 if (service == null) throw new ArgumentNullException("service");
>
> -                               if (!HasComponent(service))
> +                               if (!HasComponent(service) && !LazyLoadComponent(null, service))
>                                 {
>                                         throw new ComponentNotFoundException(service);
>                                 }
> @@ -219,7 +219,7 @@
>                         if (service == null) throw new ArgumentNullException("service");
>                         if (arguments == null) throw new ArgumentNullException("arguments");
>
> -                       if (!HasComponent(service))
> +                       if (!HasComponent(service) && !LazyLoadComponent(null, service))
>                         {
>                                 throw new ComponentNotFoundException(service);
>                         }
> @@ -253,7 +253,7 @@
>                         if (key == null) throw new ArgumentNullException("key");
>                         if (arguments == null) throw new ArgumentNullException("arguments");
>
> -                       if (!HasComponent(key))
> +                       if (!HasComponent(key) && !LazyLoadComponent(key, null))
>                         {
>                                 throw new ComponentNotFoundException(key);
>                         }
> @@ -286,7 +286,7 @@
>                         if (key == null) throw new ArgumentNullException("key");
>                         if (service == null) throw new ArgumentNullException("service");
>
> -                       if (!HasComponent(key))
> +                       if (!HasComponent(key) && !LazyLoadComponent(key, service))
>                         {
>                                 throw new ComponentNotFoundException(key);
>                         }
> @@ -319,7 +319,7 @@
>                         if (key == null) throw new ArgumentNullException("key");
>                         if (service == null) throw new ArgumentNullException("service");
>
> -                       if (!HasComponent(key))
> +                       if (!HasComponent(key) && !LazyLoadComponent(key,service))
>                         {
>                                 throw new ComponentNotFoundException(key);
>                         }
> Index: src/Castle.MicroKernel/Handlers/AbstractHandler.cs
> ===================================================================
> --- src/Castle.MicroKernel/Handlers/AbstractHandler.cs  (revision 6294)
> +++ src/Castle.MicroKernel/Handlers/AbstractHandler.cs  (working copy)
> @@ -16,6 +16,7 @@
>  {
>         using System;
>         using System.Collections;
> +       using System.Collections.Generic;
>         using System.Collections.Specialized;
>         using System.Diagnostics;
>         using System.Text;
> @@ -36,12 +37,12 @@
>                 /// <summary>
>                 /// Dictionary of Type to a list of <see cref="DependencyModel"/>
>                 /// </summary>
> -               private IDictionary dependenciesByService;
> +               private IDictionary<Type, DependencyModel> dependenciesByService;
>
>                 /// <summary>
>                 /// Dictionary of key (string) to <see cref="DependencyModel"/>
>                 /// </summary>
> -               private IDictionary dependenciesByKey;
> +               private IDictionary<string, DependencyModel> dependenciesByKey;
>
>                 /// <summary>
>                 /// Custom dependencies values associated with the handler
> @@ -292,9 +293,9 @@
>                         {
>                                 sb.Append("\r\nKeys (components with specific keys)\r\n");
>
> -                               foreach (DictionaryEntry entry in DependenciesByKey)
> +                               foreach (var dependency in DependenciesByKey)
>                                 {
> -                                       String key = entry.Key.ToString();
> +                                       String key = dependency.Key;
>
>                                         IHandler handler = Kernel.GetHandler(key);
>
> @@ -486,14 +487,14 @@
>
>                         if (dependency.DependencyType == DependencyType.Service && dependency.TargetType != null)
>                         {
> -                               if (DependenciesByService.Contains(dependency.TargetType))
> +                               if (DependenciesByService.ContainsKey(dependency.TargetType))
>                                 {
>                                         return;
>                                 }
>
>                                 DependenciesByService.Add(dependency.TargetType, dependency);
>                         }
> -                       else if (!DependenciesByKey.Contains(dependency.DependencyKey))
> +                       else if (!DependenciesByKey.ContainsKey(dependency.DependencyKey))
>                         {
>                                 DependenciesByKey.Add(dependency.DependencyKey, dependency);
>                         }
> @@ -552,11 +553,11 @@
>
>                         // Check within the Kernel
>
> -                       foreach (DictionaryEntry kvp in new Hashtable(DependenciesByService))
> +                       foreach (var pair in new Dictionary<Type,DependencyModel>(DependenciesByService))
>                         {
> -                               Type service = (Type)kvp.Key;
> -                               DependencyModel dependencyModel = (DependencyModel)kvp.Value;
> -                               if (HasValidComponent(service, dependencyModel))
> +                               Type service = pair.Key;
> +                               DependencyModel dependency = pair.Value;
> +                               if (HasValidComponent(service, dependency))
>                                 {
>                                         DependenciesByService.Remove(service);
>                                         IHandler dependingHandler = kernel.GetHandler(service);
> @@ -565,14 +566,14 @@
>                                 }
>                         }
>
> -                       foreach (DictionaryEntry kvp in new Hashtable(DependenciesByKey))
> +                       foreach (var pair in new Dictionary<string,DependencyModel>(DependenciesByKey))
>                         {
> -                               string compKey = (string)kvp.Key;
> -                               DependencyModel dependency = (DependencyModel)kvp.Value;
> -                               if (HasValidComponent(compKey, dependency) || HasCustomParameter(compKey))
> +                               string key = pair.Key;
> +                               DependencyModel dependency = pair.Value;
> +                               if (HasValidComponent(key, dependency) || HasCustomParameter(key))
>                                 {
> -                                       DependenciesByKey.Remove(compKey);
> -                                       IHandler dependingHandler = kernel.GetHandler(compKey);
> +                                       DependenciesByKey.Remove(key);
> +                                       IHandler dependingHandler = kernel.GetHandler(key);
>                                         if(dependingHandler!=null)//may not be real handler, if we are using sub resovler
>                                                 AddGraphDependency(dependingHandler.ComponentModel);
>                                 }
> @@ -648,25 +649,25 @@
>                         state = newState;
>                 }
>
> -               protected IDictionary DependenciesByService
> +               protected IDictionary<Type,DependencyModel> DependenciesByService
>                 {
>                         get
>                         {
>                                 if (dependenciesByService == null)
>                                 {
> -                                       dependenciesByService = new HybridDictionary();
> +                                       dependenciesByService = new Dictionary<Type, DependencyModel>();
>                                 }
>                                 return dependenciesByService;
>                         }
>                 }
>
> -               protected IDictionary DependenciesByKey
> +               protected IDictionary<string, DependencyModel> DependenciesByKey
>                 {
>                         get
>                         {
>                                 if (dependenciesByKey == null)
>                                 {
> -                                       dependenciesByKey = new HybridDictionary();
> +                                       dependenciesByKey = new Dictionary<string, DependencyModel>();
>                                 }
>                                 return dependenciesByKey;
>                         }
> @@ -727,7 +728,7 @@
>                         ComponentModel.AddDependent(model);
>                 }
>
> -               private DependencyModel[] Union(ICollection firstset, ICollection secondset)
> +               private DependencyModel[] Union(ICollection<DependencyModel> firstset, ICollection<DependencyModel> secondset)
>                 {
>                         DependencyModel[] result = new DependencyModel[firstset.Count + secondset.Count];
>
> Index: src/Castle.MicroKernel/Handlers/DefaultHandler.cs
> ===================================================================
> --- src/Castle.MicroKernel/Handlers/DefaultHandler.cs   (revision 6294)
> +++ src/Castle.MicroKernel/Handlers/DefaultHandler.cs   (working copy)
> @@ -16,6 +16,8 @@
>  {
>         using System;
>         using System.Collections;
> +       using System.Linq;
> +
>         using Castle.Core;
>
>         /// <summary>
> @@ -66,18 +68,23 @@
>                         if (context.HandlerIsCurrentlyBeingResolved(this))
>                                 return false;
>
> -            foreach (Type service in DependenciesByService.Keys)
> +                       foreach (var dependency in DependenciesByService.Values.ToArray())
>                         {
>                                 // a self-dependency is not allowed
> -                               var handler = Kernel.GetHandler(service);
> +                               var handler = Kernel.GetHandler(dependency.TargetType);
>                                 if (handler == this)
>                                         return false;
>
>                                 // ask the kernel
> -                               if (!Kernel.HasComponent(service))
> +                               if (Kernel.HasComponent(dependency.TargetType)) continue;
> +
> +                               // let's try to lazy load the dependency...
> +                               if (!Kernel.LazyLoadComponent(dependency.DependencyKey, dependency.TargetType))
>                                         return false;
> +                               // and see if we can have it this time around
> +                               if (!Kernel.HasComponent(dependency.TargetType))
> +                                       return false;
>                         }
> -                      
>                         return DependenciesByKey.Count == 0;
>                 }
>
> Index: src/Castle.MicroKernel/IKernel_Resolve.cs
> ===================================================================
> --- src/Castle.MicroKernel/IKernel_Resolve.cs   (revision 6294)
> +++ src/Castle.MicroKernel/IKernel_Resolve.cs   (working copy)
> @@ -17,6 +17,7 @@
>         using System;
>         using System.Collections;
>         using Castle.Core;
> +       using Castle.MicroKernel.SubSystems.Naming;
>
>         public partial interface IKernel : IServiceProviderEx, IKernelEvents, IDisposable
>         {
> @@ -170,5 +171,14 @@
>                 /// <param name="arguments"></param>
>                 /// <returns></returns>
>                 object Resolve(String key, Type service, IDictionary arguments);
> +
> +               /// <summary>
> +               /// Tries to perform on-demand loading of component for specified <paramref name="key"/> and <paramref name="service"/>
> +               /// using <see cref="ILazyComponentLoader"/> instances registered with the kernel.
> +               /// </summary>
> +               /// <param name="key">The key of requested component.</param>
> +               /// <param name="service">The type of requested service.</param>
> +               /// <returns>True if any of <see cref="ILazyComponentLoader"/> instances did register the service, false otherwise.</returns>
> +               bool LazyLoadComponent(string key, Type service);
>         }
>  }
> Index: src/Castle.MicroKernel/SubSystems/Naming/DefaultNamingSubSystem.cs
> ===================================================================
> --- src/Castle.MicroKernel/SubSystems/Naming/DefaultNamingSubSystem.cs  (revision 6294)
> +++ src/Castle.MicroKernel/SubSystems/Naming/DefaultNamingSubSystem.cs  (working copy)
> @@ -423,6 +423,23 @@
>                         selectors.Add(selector);
>                 }
>
> +               public bool LoadComponent(string key, Type service)
> +               {
> +                       foreach (var resolver in Kernel.ResolveAll<ILazyComponentLoader>())
> +                       {
> +                               if (resolver.CanLoad(key, service))
> +                               {
> +                                       var registration = resolver.Load(key, service);
> +                                       if (registration != null)
> +                                       {
> +                                               registration.Register(Kernel);
> +                                               return true;
> +                                       }
> +                               }
> +                       }
> +                       return false;
> +               }
> +
>                 protected virtual IHandler GetSelectorsOpinion(string key, Type type)
>                 {
>                         type = type ?? typeof(object);// if type is null, we want everything, so object does well for that
> Index: src/Castle.MicroKernel/SubSystems/Naming/ILazyComponentLoader.cs
> ===================================================================
> --- src/Castle.MicroKernel/SubSystems/Naming/ILazyComponentLoader.cs    (revision 0)
> +++ src/Castle.MicroKernel/SubSystems/Naming/ILazyComponentLoader.cs    (revision 0)
> @@ -0,0 +1,13 @@
> +namespace Castle.MicroKernel.SubSystems.Naming
> +{
> +       using System;
> +
> +       using Castle.MicroKernel.Registration;
> +
> +       public interface ILazyComponentLoader
> +       {
> +               bool CanLoad(string key, Type service);
> +
> +               IRegistration Load(string key, Type service);
> +       }
> +}
> \ No newline at end of file
> Index: src/Castle.MicroKernel/SubSystems/Naming/INamingSubSystem.cs
> ===================================================================
> --- src/Castle.MicroKernel/SubSystems/Naming/INamingSubSystem.cs        (revision 6294)
> +++ src/Castle.MicroKernel/SubSystems/Naming/INamingSubSystem.cs        (working copy)
> @@ -148,5 +148,13 @@
>          /// making about which handler to resolve
>          /// </summary>
>             void AddHandlerSelector(IHandlerSelector selector);
> +
> +               /// <summary>
> +               /// Tries to perform just in time registration of component for given <paramref name="key"/> and <paramref name="service"/> to the container.
> +               /// </summary>
> +               /// <param name="key">The key of requested component.</param>
> +               /// <param name="service">The type of requested service.</param>
> +               /// <returns>True if the component was registered with the container, false otherwise</returns>
> +               bool LoadComponent(string key, Type service);
>         }
>  }

Ayende Rahien

unread,
Nov 2, 2009, 4:44:42 AM11/2/09
to castle-pro...@googlegroups.com
Just FYI, I am currently _very_ busy, I'll try looking at this on Sunday.

2009/10/30 Krzysztof Koźmic <krzyszto...@gmail.com>
+namespace Castle.MicroKernel.SubSystems.Naming

Krzysztof Koźmic (2)

unread,
Nov 2, 2009, 4:49:03 AM11/2/09
to Castle Project Development List
sure no problem,

I'll have some feedback from actual big project trying to use this to
integrate with MEF this week, so I may incorporate some changes based
on this feedback during this week as well.

On 2 Lis, 10:44, Ayende Rahien <aye...@ayende.com> wrote:
> Just FYI, I am currently _very_ busy, I'll try looking at this on Sunday.
>

> 2009/10/30 Krzysztof Koźmic <krzysztof.koz...@gmail.com>

> ...
>
> więcej »

Mauricio Scheffer

unread,
Nov 2, 2009, 10:14:50 AM11/2/09
to Castle Project Development List
How about committing the patch, along with any MEF tests, to a branch,
to make reviewing easier?
Or you could fork http://github.com/mausch/Castle.InversionOfControl
and commit there.
> ...
>
> read more >>
Reply all
Reply to author
Forward
0 new messages