Can i see with what lifetime a component is registered in IoC?

It is possible to define lifetime when registering a service with IoC, but can I ask the container after registration which lifetime is set?

Case: I want to write a unit test to verify correct lifetime and fail a deployment if this gets modified to the wrong value.

Litium version: 7.4

Litium 7.4 is using Castle Windsor and they have methods to get registered components, see https://stackoverflow.com/questions/1550190/list-all-types-registered-with-a-castle-windsor-container-instance for how to list the different components.

The Castle Windsor container can be injected with the Castle.Windsor.IWindsorContainer interface in your service.

That worked, full test below, with lifetime Scoped i could not test just against LifestyleType (that would have worked if my interface was a Singleton) so i had to test the type of CustomLifestyle.

Example below:

[Fact]
public void Should_have_lifetime_scoped()
{
    var container = IoC.Resolve<IWindsorContainer>() as WindsorContainer;
    container.ShouldNotBeNull();

    var customerService = container.Kernel.GetAssignableHandlers(typeof(ICustomerService));
    customerService.ShouldNotBeNull();

    var componentModel = customerService[0].ComponentModel;
    componentModel.LifestyleType.ShouldBe(LifestyleType.Custom);
    componentModel.CustomLifestyle.BaseType.Name.ShouldBe("ScopedLifestyleManager");
}

This will only be a part of the solution, you also need to identify the service that is injected as ctor parameters to ensure that not an singleton is injecting a scoped service.

Good point, here is the updated test, implemented a recursive check on constructors to verify that a singleton is not injecting a non-singleton (I have not implemented support for transient).

using System;
using System.Collections.Generic;
using Castle.Core;
using Castle.Windsor;
using Litium.Accelerator.Services;
using Litium.Runtime.DependencyInjection;
using Shouldly;
using Xunit;

namespace Litium.Accelerator.Tests.Common
{
    public class DependencyInjectionTests : LitiumApplicationTestBase
    {
        [Theory]
        [InlineData(typeof(ICustomerService), DependencyLifetime.Scoped)]
        [InlineData(typeof(IWorkshopService), DependencyLifetime.Scoped)]
        public void Component_should_be_registered_with_lifetime(Type registeredComponentType, DependencyLifetime dependencyLifetime)
        {
            var container = IoC.Resolve<IWindsorContainer>() as WindsorContainer;
            container.ShouldNotBeNull();

            VerifyTypeLifetime(container, registeredComponentType, dependencyLifetime, new List<string>());
        }

        private void VerifyTypeLifetime(WindsorContainer container, Type registeredComponentType, DependencyLifetime dependencyLifetime, List<string> callPath, string customMessage = null)
        {
            var registeredComponent = container.Kernel.GetAssignableHandlers(registeredComponentType);
            registeredComponent.ShouldNotBeNull($"No registered component found for '{registeredComponent}'");
            var componentModel = registeredComponent[0].ComponentModel;

            callPath.Add(componentModel.Name);
            var message = $"{customMessage} Path:'{string.Join(" > ", callPath)}'";

            if (dependencyLifetime == DependencyLifetime.Scoped)
            {
                componentModel.LifestyleType.ShouldBe(LifestyleType.Custom, message);
                componentModel.CustomLifestyle.BaseType.Name.ShouldBe("ScopedLifestyleManager", message);
            }
            else if (dependencyLifetime == DependencyLifetime.Singleton)
            {
                componentModel.LifestyleType.ShouldBe(LifestyleType.Singleton, message);

                // If the current service is a singleton, verify that it is not injecting a scoped service!
                var constructors = componentModel.Implementation.GetConstructors();
                foreach (var constructor in constructors)
                {
                    var constructorParams = constructor.GetParameters();
                    foreach (var constructorParam in constructorParams)
                    {
                        var lifetimeWarnMsg = "A SINGLETON SERVICE SHOULD NOT INJECT A SCOPED OR TRANSIENT SERVICE";
                        VerifyTypeLifetime(container, constructorParam.ParameterType, DependencyLifetime.Singleton, callPath, lifetimeWarnMsg);
                    }
                }
            }
            else
            {
                true.ShouldBeFalse($"{message} : Lifetime {dependencyLifetime} is not yet supported in test");
            }
        }
    }
}

Using a transient service in a singleton or scoped service is not a problem, so that warning should probably be removed from the recursive check (refer to lifetimeWarnMsg).

As long as the injected service isn’t stored in the class any service can be injected, I’ll implement support for exceptions to the rule if those issues come up.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.