Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate named registrations fails silently when using BasedOnDescriptor.Configure #650

Open
jholzer-cciq opened this issue May 18, 2023 · 1 comment

Comments

@jholzer-cciq
Copy link

jholzer-cciq commented May 18, 2023

In my application, I'm using a custom attribute to provide info to Windsor that it can use to generate named registrations for different implementations of the same underlying service type.

The issue is that if a scenario is encountered where the same name would be used to register multiple components, then it creates a race condition, where the first type that Windsor discovers and registers will get added to the container, while the other types that try to register with the same name, simply do not get added to the container. No exception is triggered, which is the behavior I expected.

Unfortunately, I can't share the exact code, but here is some similar code that should reproduce the same issue. Whichever type is discovered by Windsor first , either ServiceImplementation1 or ServiceImplementation2, will get registered, while the attempt to register the 2nd component with the same name will fail silently.

namespace MyApp
{
    [AttributeUsage(AttributeTargets.Class)]
    public class CustomAttribute : Attribute
    {
        public string Key { get; }

        public CustomAttribute(string key)
        {
            Key = key;
        }
    }

    // the service type registered with the container
    public interface IServiceInterface { }

    // implementation #1
    [Custom("implementation1")]
    public class ServiceImplementation1 : IServiceInterface { }

    // implementation #2: this uses the same key as #1, to trigger a registration failure
    [Custom("implementation1")]
    public class ServiceImplementation2 : IServiceInterface { }

    public class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Classes.FromAssemblyContaining<WindsorInstaller>()
                    .Where(t => typeof(IServiceInterface).IsAssignableFrom(t) &&
                                Component.HasAttribute<CustomAttribute>(t))
                    .Configure(reg =>
                    {
                        var attribute =
                            (CustomAttribute)Attribute.GetCustomAttribute(c.Implementation, typeof(CustomAttribute));
                        reg.Named($"{typeof(IServiceInterface).FullName}_{attribute.Key}");
                    })
            );


        }
    }
    
    public class Program
    {
        public static int Main(string[] args)
        {
            var container = new WindsorContainer().Install(FromAssembly.Containing<Program>());

            // resolves to whichever component happened to be discovered first
            var service = container.Resolve<IServiceInterface>($"{typeof(IServiceInterface).FullName}_implementation1");
            Console.WriteLine($"Resolved {service.GetType().Name}");

            // returns a single component (whichever one was successfully registered)
            var allServices = container.ResolveAll<IServiceInterface>();
            Console.WriteLine($"Discovered {allServices.Length} services that implement IServiceInterface");

            return 0;
        }
    }
}
@jholzer-cciq
Copy link
Author

jholzer-cciq commented May 18, 2023

FWIW... I discovered a way to deal with this issue by using the IKernel.HasComponent method to explicitly check for a duplicate named component. This still feels like something that Windsor should do by default though.

From the sample code I included earlier, here's how I changed the registration to throw an exception

container.Register(
    Classes.FromAssemblyContaining<WindsorInstaller>()
        .Where(t => typeof(IServiceInterface).IsAssignableFrom(t) && Component.HasAttribute<CustomAttribute>(t))
        .Configure(reg =>
        {
            var attribute = (CustomAttribute)Attribute.GetCustomAttribute(c.Implementation, typeof(CustomAttribute));

            var componentName = $"{typeof(IServiceInterface).FullName}_{attribute.Key}";

            // throw an exception when trying to register another component with same name
            if (container.Kernel.HasComponent(componentName))
                throw new ComponentRegistrationException("some useful message about registration failure");

            reg.Named(componentName);
        })
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant