Register the same Service multiple times, from XML and Installer (to allow overriding them from XML)

822 views
Skip to first unread message

BhaaL DoesNotUseGplus

unread,
Apr 20, 2015, 10:09:28 AM4/20/15
to castle-pro...@googlegroups.com
In my application, i use the following Install call:
 
container.Install(Configuration.FromAppConfig(), FromAssembly.This());



In the most preferable case, the app.config has an empty configuration block, with everything else being done by Installer classes.
However, one reason why I'd like the app.config in front is that I can override whatever the Installer decides...at least thats what I initially thought was possible. My use case would be to provide a small assembly containing one class containing a bugfix, and configuring it using XML to replace the component registered by the installers.

Trigger was this line from the documentation:
In Windsor first one wins

In Castle, the default implementation for a service is the first registered implementation.[...]

Now I actually tried to do this; and was faced with a ComponentRegistrationException stating that the component is already present.

The component I tried to replace was registered without an explicit name (so it uses Type.FullName), but even when providing a Name the Exception is raised (obviously, since Castle.MicroKernel.SubSystems.Naming.DefaultNamingSubSystem tries Dictionary.Add and throws if something happens).

So, the installer basically does this:
 
container.Register(Component.For<ApplicationNamespace.IService>().ImplementedBy<ApplicationNamespace.ComponentClass>());


Even specifying the usual suspects of IsFallback() or explicitly naming the services has the same result.

And the XML does this:
 
<component id="ApplicationNamespace.ComponentClass" service="ApplicationNamespace.IService" type="ReplacementNamespace.ComponentClass, ReplacementAssembly"/>



Surprisingly, I can actually do this both from within Code and within XML (registering the same service multiple times); but it seems I cannot mix those two.

Is there any built-in mechanism to support this scenario, or what would I have to do to support this on my own?

hammett

unread,
Apr 20, 2015, 7:10:11 PM4/20/15
to castle-pro...@googlegroups.com
If you try

Component.For<YourComponent>().Named("somethingelse")

It should work.

That said, your composition will still use the first one registered,
unless you rewire things somehow. That's why the xml config "works",
because it's registering stuff before everything else. If you change
the key there (the "id"), then you'll get the expected behavior -- if
I understood your problem correctly.
> --
> You received this message because you are subscribed to the Google Groups
> "Castle Project Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to castle-project-u...@googlegroups.com.
> To post to this group, send email to castle-pro...@googlegroups.com.
> Visit this group at http://groups.google.com/group/castle-project-users.
> For more options, visit https://groups.google.com/d/optout.



--
Cheers,
hammett
http://www.d-collab.com/
http://www.hammettblog.com/

BhaaL DoesNotUseGplus

unread,
Jun 11, 2015, 7:31:38 AM6/11/15
to castle-pro...@googlegroups.com
Sorry for the delay, mail notification apparently got lost and I just revisited that issue...

Well, not really, unfortunately this doesn't work for me. It should, as you said; but instead I get this exception thrown (with no mention of my installer class):

Castle.MicroKernel.ComponentRegistrationException was unhandled
  HelpLink=groups.google.com/group/castle-project-users
  HResult=-2146233088
  Message=Component somethingelse could not be registered. There is already a component with that name. Did you want to modify the existing component instead? If not, make sure you specify a unique name.
  Source=Castle.Windsor
  StackTrace:
       at Castle.MicroKernel.SubSystems.Naming.DefaultNamingSubSystem.Register(IHandler handler)
       at Castle.MicroKernel.DefaultKernel.AddCustomComponent(ComponentModel model)
       at Castle.MicroKernel.Registration.ComponentRegistration`1.Castle.MicroKernel.Registration.IRegistration.Register(IKernelInternal kernel)
       at Castle.MicroKernel.DefaultKernel.Register(IRegistration[] registrations)
       at Castle.Windsor.WindsorContainer.Register(IRegistration[] registrations)
       at Castle.Windsor.Installer.DefaultComponentInstaller.SetUpComponents(IConfiguration[] configurations, IWindsorContainer container, IConversionManager converter)
       at Castle.Windsor.Installer.DefaultComponentInstaller.SetUp(IWindsorContainer container, IConfigurationStore store)
       at Castle.Windsor.WindsorContainer.Install(IWindsorInstaller[] installers, DefaultComponentInstaller scope)
       at Castle.Windsor.WindsorContainer.Install(IWindsorInstaller[] installers)
       at ApplicationNamespace.ServiceRunner..ctor() in e:\_dev\TestApplication\Infrastructure\ServiceRunner.cs:line 24
       at ApplicationNamespace.ServiceRunner.Start() in e:\_dev\TestApplication\Infrastructure\ServiceRunner.cs:line 40
       at ApplicationNamespace.Program.Main(String[] args) in e:\_dev\TestApplication\Program.cs:line 10
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:


Actually, my initial reason for creating this thread was because of a Service that isn't registered manually but by convention (using Classes.FromAssemblyInThisApplication.BaseOn<ISomeInterface>().WithServiceSelf()) that didn't have an explicit name assigned. But even with named services registered like in your example, that exception is thrown either way.

I just tried something else; and that is calling container.Install twice. First with the App.config, then with the Installers using FromAssembly.This(). The result is the same, altho the Stacktrace for the exception looks different. This time, it stops inside my Installer class where the line
container.Register(Component.For<IService>().ImplementedBy<SomeClass>().Named("somethingelse"));
is.

The scenario is as follows (hopefully clearer than in the initial post):
1. My container.Install call looks like this: container.Install(Configuration.FromAppConfig(), FromAssembly.This()); (assuming that App.config comes first, with the idea that I can override Installers when necessary).
2. One of the installer classes registers a service like that: container.Register(Component.For<IService>().ImplementedBy<SomeClass>().Named("myservice"));
3. The default App.config has no <component> entry for "myservice" at all. In fact, it is an empty <castle> block with nothing inside.
4. The application is shipped to a customer, and they notice a problem with SomeClass.
5. I create a new Class Library project, implement a new version of IService that works, and send it to the customer. In their environment, they put the DLL in the install folder of the application and modify App.config to add the additional line <component id="myservice" service="ApplicationNamespace.IService" type="Bugfix.SomeOtherClass, BugfixDLL"/>

My intention was that, by doing above steps, I get this result:
- with the <component> line in App.config: Bugfix.SomeOtherClass is used whenever "myservice" is requested. The original SomeClass is never used.
- without the <component> line in App.config: The original SomeClass is used all the time when "myservice" is requested.

Somehow, I think this might be a bug, since you suggested earlier that this should actually work?

- BhaaL

BhaaL DoesNotUseGplus

unread,
Oct 14, 2016, 2:21:52 AM10/14/16
to Castle Project Users
Since I'm probably not going to find the time to actually implement this soon-ish, I'll post my current findings here (so I can remember what I already tried; or maybe someone else can pick it up from there).

For whatever reason, the "first one wins" idea doesn't apply to named components/services as soon as they come from different sources.
However, there is a way of making this work by calling ComponentRegistration.RegisterOptionally - which unfortunately is internal.

Using Reflection (which is a PITA to maintain since we'd depend on implementation details), I can actually call that method to make certain specific named components/services overridable; and achieve my desired result:
- the App.config registers a named service that should be used instead
- the hardcoded named service from the Installer class is ignored

The goal would/could be to package this into some sort of Facility, so we don't have to dirty-hack around with implementation details; but at the same time, the Facility would face the same issue (unless it were part of Windsor).

I'll just leave it at that for now; but if anyone has some good reasons against doing it this way, please speak up.
Otherwise, thanks for reading.

Ken Egozi

unread,
Oct 14, 2016, 3:43:08 AM10/14/16
to castle-pro...@googlegroups.com
A cleaner (and possibly clearer) way to achieve an override ability is to use a custom IHandlerSelector, in which you can pick an override if it exist.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to castle-project-users+unsub...@googlegroups.com.
To post to this group, send email to castle-project-users@googlegroups.com.
Visit this group at https://groups.google.com/group/castle-project-users.

BhaaL DoesNotUseGplus

unread,
Nov 11, 2016, 1:57:43 AM11/11/16
to Castle Project Users
Could you elaborate on that?
The problem is that I already have multiple implementations for some of the services (which is: the ones that do have names; and therefore cause problems with this approach) - and they're named so I can use a custom IHandlerSelector to pick the right one for a given job (which, in some cases, even includes a composite services that gets two other ones to merge their results together). Plus, I do not register them both in Code with an Installer (which would work!), but I register one of them in the Config and the other from Code (which is why I'm confused on how one of them can work but the other behaves differently).

The only way I could see this work is by using a different name for the updated component that follows some sort of convention (like "Override" + the original name), so the IHandlerSelector could do its thing...and, actually, writing about it does make sense! I'll give that a try once I find some free time to do so.

Thanks!
Reply all
Reply to author
Forward
0 new messages