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

Trying to load configuration from Installer results in duplicate registrations #95

Closed
richardspence opened this issue Aug 10, 2015 · 6 comments

Comments

@richardspence
Copy link

This is my first time playing with Castle, so maybe I'm doing something idiotic:

I'm creating a new Container that loads an xml file. That xml file loads a custom installer. The custom installer will feed additional xml files. Doing this components fed at the custom installer level are somehow registered twice causing a typeinitializer exception.

Here is some of the code

 internal class CastleContainer
    {
        static CastleContainer()
        {
            var path = System.AppDomain.CurrentDomain.BaseDirectory+"Windsor.config";
            Current = new WindsorContainer(new XmlInterpreter(path));
        }
        public static IWindsorContainer Current { get; internal set; }
    }
    public class DatabaseInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            var source = new InMemoryResource();
            ////XmlInterpreter xi = new XmlInterpreter(source);
            ////xi.ProcessResource(xi.Source, store, container.Kernel);
            //var kernel = container.Kernel;
            //IResourceSubSystem resourceSubSystem = kernel.GetSubSystem(SubSystemConstants.ResourceKey) as IResourceSubSystem;
            //resourceSubSystem.RegisterResourceFactory(new DatabaseResourceFactory());

            container.Install(Configuration.FromXml(source));
        }

        internal class DatabaseResourceFactory : IResourceFactory
        {
            public bool Accept(CustomUri uri)
            {
                return "db".Equals(uri.Scheme);
            }

            public IResource Create(CustomUri uri)
            {
                return this.Create(uri, null);
            }

            public IResource Create(CustomUri uri, string basePath)
            {
                return new InMemoryResource();
            }
        }

        public class InMemoryResource : IResource
        {
            public string FileBasePath
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            public IResource CreateRelative(string relativePath)
            {
                throw new NotImplementedException();
            }

            public void Dispose()
            {
            }

            public TextReader GetStreamReader()
            {
                var config = @"<configuration>  <components>    <component id=""ConfigRepository"" service=""SimpleService.IMyService,SimpleService"" type=""SimpleService.MyService2,SimpleService"" >      <parameters>        <innerRepositoryKey>ConfigRepository.nh</innerRepositoryKey>      </parameters>    </component>  </components></configuration> ";
                return (new StringReader(config));
            }

            public TextReader GetStreamReader(Encoding encoding)
            {
                return GetStreamReader();
            }
        }
    }
<configuration>
  <installers>
    <install assembly="WindsorSandbox"/>
  </installers>
</configuration>
@jonorossi
Copy link
Member

@richardspence this looks like an unsupported use case. From a quick look at the code it looks like the configuration your installer is "installing" gets loaded into the PartialConfigurationStore of the parent DatabaseInstaller and so gets set up twice.

I probably wouldn't recommend loading an installer inside another installer. Do you want to describe what you are trying to do, as there is likely a better way.

FYI, no need to make your own InMemoryResource class, there is Castle.Core.Resource.StaticContentResource.

@richardspence
Copy link
Author

Thanks @jonorossi, I was able to get this to work by using the default constructor. So there appears to be a bug when using the XmlInterpreter constructor

new WindsorContainer().Install(Configuration.FromXmlFile(....)); //works when this 
new WindsorContainer(new XmlInterpreter(...)); //does not

Although this fixed my above issue it didn't really help me that much. I was planning on registering a new resource factory within the new installer, but doing so doesn't allow you to use the custom uris within the parent (even if declared after the installers) since it looks as tho installers aren't erxecuted until the xml file is parsed. So I'll prob just change my approach here.

Ultimately I wanted to drive all Windsor config and subsequent installers from a central config file. I've done the same sort of thing with Unity IoC with configuration and extensions.

@richardspence
Copy link
Author

Without debugging the code to ensure this is the problem, but it appears that the WindsorContainer has a flaw in the ctor chainging that results in RunInstaller() being called twice for most ctors. Removing the calls to RunInstaller except for the most compex ctor might do the trick.

@jonorossi
Copy link
Member

All the constructors look good to me. This is the stack for your example, RunInstaller is run once as the last thing before the constructor chain returns:

public WindsorContainer(IConfigurationInterpreter interpreter)
    : this()
{
    ...
    RunInstaller();
}
public WindsorContainer()
    : this(new DefaultKernel(), new DefaultComponentInstaller())
public WindsorContainer(IKernel kernel, IComponentsInstaller installer)
    : this(AppDomain.CurrentDomain.FriendlyName + CastleUnicode + ++instanceCount, kernel, installer)
public WindsorContainer(String name, IKernel kernel, IComponentsInstaller installer)

If you weren't aware Configuration.FromXmlFile uses an installer to add the configuration to the store, so these lines are equivalent, however you should still use the first, this is just to hopefully make the next explanation easier to understand:

container.Install(Configuration.FromXmlFile(path));
container.Install(new ConfigurationInstaller(new XmlInterpreter(path)));

Actually debugging the code it is similar to what I mentioned in my last comment. That constructor uses kernel.ConfigurationStore directly and doesn't create a PartialConfigurationStore to record only what was installed in its scope. As you saw that constructor calls RunInstaller:

  • which calls DefaultComponentInstaller.SetUp
  • which calls DefaultComponentInstaller.SetUpInstallers
  • which ends up calling WindsorContainer.Install(IWindsorInstaller[]) with an instantiated DatabaseInstaller
  • which calls DatabaseInstaller.Install
  • WindsorContainer.Install finishes its call by calling DefaultComponentInstaller.SetUp to handle setting up any installers, facilities and components added to the configuration by your installer
  • at this point the container has got your ConfigRepository component registered because SetUpInstallers, SetUpFacilities and SetUpComponents have been called.
  • the first call to DefaultComponentInstaller.SetUp then continues and calls SetUpFacilities again, and SetUpComponents again.
  • this call to SetUpComponents then tries to register your component again because it is in the IConfigurationStore.

To fix this I think we'd need to create a PartialConfigurationStore to track what was added by using the IConfigurationInterpreter overload, or somehow rearrange things so all the overloads go via the methods you'd call if you didn't use the constructor.

@jonorossi
Copy link
Member

@richardspence lets use this issue for the defect you found, however...

Although this fixed my above issue it didn't really help me that much. I was planning on registering a new resource factory within the new installer, but doing so doesn't allow you to use the custom uris within the parent (even if declared after the installers) since it looks as tho installers aren't erxecuted until the xml file is parsed. So I'll prob just change my approach here.

Ultimately I wanted to drive all Windsor config and subsequent installers from a central config file. I've done the same sort of thing with Unity IoC with configuration and extensions.

I'm not sure what you actually mean. If you want to explore it further jump on the users mailing list or open another issue. Thanks.

@ghost
Copy link

ghost commented Sep 19, 2017

Please join the discussion on #338 I am proposing we deprecate XML configs. Closing this for now.

@ghost ghost closed this as completed Sep 19, 2017
This issue was closed.
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

2 participants