Search code examples
.netinversion-of-controlunity-containercastle-windsorioc-container

Unity IoC - ComponentCreated / OnActivating / RegisterInitializer events


I'm using the Microsoft Unity IOC container and I want to be able to raise an event every time a component is created (not registered) i.e. Lifetime events

For example

  • in Castle Windosor they have the ComponentCreated event.
  • In the Autofac they have Lifetime events OnActivating and OnRelease events.
  • in Simple Injector they have RegisterInitializer and Options.RegisterResolveInterceptor methods.
  • etc..

What is the equivalent in Unity?


Solution

  • Out of the box Unity does not have notifications. However, Unity provides the ability to extend the container functionality using a UnityContainerExtension which would let you add notifications fairly easily.

    There are a variety of stages where the container can hook into the build pipeline by specifying what part of the UnityBuildStage you want the extension to run at (stages are Setup, TypeMapping, Lifetime, PreCreation, Creation, Initialization, PostInitialization). (It can get a bit tricky because the pipeline might short circuit as in the case of using the 'singleton' ContainerControlledLifetimeManager where the pipeline stops at Lifetime.)

    In a container extension there are already two events that notify of registrations: Registering and RegisteringInstance. Those could be useful if you care about registrations.

    If you wanted to receive a notification when an object is resolved or torn down then you could write a container extension similar to this:

    public class BuildNotificationExtension : UnityContainerExtension
    {
        public event EventHandler<NamedTypeBuildKey> BuiltUp;
        public event EventHandler<object> TearDown;
    
        protected override void Initialize()
        {
            var builderAwareStrategy = new CustomBuilderAwareStrategy(this.BuiltUp, this.TearDown);
            // Run before Lifetime so we know about all resolved calls
            // even if singleton that is not actually built up
            base.Context.Strategies.Add(builderAwareStrategy, UnityBuildStage.TypeMapping);
        }
    
        private class CustomBuilderAwareStrategy : BuilderStrategy
        {
            private event EventHandler<NamedTypeBuildKey> BuiltUp;
            private event EventHandler<object> TearDown;
    
            public CustomBuilderAwareStrategy(EventHandler<NamedTypeBuildKey> builtUp,
                EventHandler<object> tearDown)
            {
                this.BuiltUp = builtUp;
                this.TearDown = tearDown;
            }
    
            public override void PreBuildUp(IBuilderContext context)
            {
                this.BuiltUp?.Invoke(this, context.BuildKey);
            }
    
            public override void PreTearDown(IBuilderContext context)
            {
                this.TearDown?.Invoke(this, context.Existing);
            }
        }
    }
    

    And if you ran this console app:

    class Program
    {
        private class Foo {}
        private interface ILogger { }
    
        private class Logger : ILogger
        {
            public Logger(Foo foo1,  Foo foo2) { }
        }
    
        static void Main(string[] args)
        {
            var buildNotificationExtension = new BuildNotificationExtension();
            buildNotificationExtension.BuiltUp += (sender, key) =>
            {
                Console.WriteLine($"Built up type '{key.Type.Name}' with name '{key.Name}'.");
            };
            buildNotificationExtension.TearDown += (sender, obj) =>
            {
                Console.WriteLine($"Tear down type '{obj.GetType().Name}'.");
            };
    
            IUnityContainer container = new UnityContainer()
                .AddExtension(buildNotificationExtension)
                // Register ILogger as singleton
                .RegisterType<ILogger, Logger>(new ContainerControlledLifetimeManager());
    
            var logger = container.Resolve<ILogger>();
            var anotherLogger = container.Resolve<ILogger>();
    
            container.Teardown(logger);
        }
    }
    

    You would receive the following output:

    Built up type 'Logger' with name ''.
    Built up type 'Foo' with name ''.
    Built up type 'Foo' with name ''.
    Built up type 'Logger' with name ''.
    Tear down type 'Logger'.
    

    Showing that first Logger was resolved which caused two Foo instances to be resolved. Then Logger was resolved again but since it's a singleton the current Logger instance was returned and no new Foo instances were instantiated. Finally the Logger was torn down.

    This approach can be used to add additional notifications to different stages of Unity processing pipeline. e.g. you could modify the BuildNotificationExtension class to accept multiple stages and add notifications to each stage.