How to overwrite a component with castle windsor?
Asked Answered
H

6

17

I want to redefine an (default) implementation in a given windsor-container. Is that what OverWrite is for? Doesn't work, though.

container.Register(
                    Component.For<IServiceOperationAuthorization>()
                            .OverWrite()
                            .Instance(_authorization)
                    );

Any other Ideas?

cheers, Lars

Herbherbaceous answered 18/3, 2009 at 13:55 Comment(0)
H
1

I actually found a nice solution where I combine xml-files for overrides and use fluent registrations for defaults.

The fluent-API takes the fullname of a impl as the default key. On the fly I override the id's of the xml-config to imitate the key-conventions of the fluent-API.

Then i register the xml-config while I listen to Kernel.ComponentRegistered.

Afterwards I only add services from the code-config where the xml hasn't yet defined the service.

(it's a while ago and I just copy-pasted the code. hopefully you get it working. I'll do edits if you find any problems)

IList<Type> unnamedServices = new List<Type>();
IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

container.Kernel.ComponentRegistered += registered;

// The method that captures the services
private static ComponentDataDelegate captureRegistrations(
    IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
{
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                       namedServices.Add(key, handler.Service);
                   }
               };
}

After that before I register services in code, I check if they already have been registered. I also created a base class that makes this more easy. This is an application configuration:

public class ApplicationConfiguration : WindsorConfigurationSkeleton
{
    internal static WindsorServiceLocator create()
    {
        var container = createWith(null, "components-config.xml", coreServices, caches, roles);
        return new WindsorServiceLocator(container);
    }

    internal static IEnumerable<IRegistration> coreServices()
    {
        yield return Component.For<ISystemClock>()
            .ImplementedBy<PreciseSystemClock>()
            .Parameters(Parameter.ForKey("synchronizePeriodSeconds").Eq("10"))
            .LifeStyle.Singleton;

        yield return Component.For<IMailService>()
            .ImplementedBy<MailQueueService>()
            .LifeStyle.Singleton;
    }

    internal static IEnumerable<IRegistration> caches()
    {
        yield return Component.For<IDataCache<ServiceAttributes>>()
            .ImplementedBy<NoDataCache<ServiceAttributes>>()
            .LifeStyle.Singleton;

        // ....
    }
}

The base class that does the wiring: (Logging is from Commons.Logging)

public class WindsorConfigurationSkeleton
{
    private static readonly ILog _log = LogManager.GetLogger(
        typeof(WindsorConfigurationSkeleton));

    internal static IWindsorContainer createWith(
        IRegistration[] customs, string configFile, params Func<IEnumerable<IRegistration>>[] methods)
    {
        IWindsorContainer container = new WindsorContainer();
        BugFix.Kernel = container.Kernel;

        container.AddFacility("factory.support", new FactorySupportFacility());

        IList<Type> unnamedServices = new List<Type>();
        IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

        ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

        container.Kernel.ComponentRegistered += registered;

        if (customs != null)
        {
            container.Register(customs);
        }

        if (configFile != null)
        {
            tryAddXmlConfig(container, configFile);
        }

        container.Kernel.ComponentRegistered -= registered;

        if (methods != null && methods.Length > 0)
        {
            container.Register(union(unnamedServices, namedServices, methods));
        }

        return container;
    }

    private static ComponentDataDelegate captureRegistrations(
        IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
    {
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                        var text = unnamedServices.Contains(handler.Service) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {2} service for {0} with {1}.",
                                    handler.Service.GetDisplayName(),
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));

                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                        var text = namedServices.ContainsKey(key) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {3} service {0} with name '{1}' and {2}.",
                                    handler.ComponentModel.Service,
                                    handler.ComponentModel.Name,
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));
                       namedServices.Add(key, handler.Service);
                   }
               };
    }

    protected static void tryAddXmlConfig(IWindsorContainer container, string filename)
    {
        var fi = Resources.GetFileFromResourceHierarchy(typeof(ApplicationContext).Namespace, filename);
        if ( fi == null ) {
            return;
        }
        var configFile = fi.FullName;
        var xd = immitateFluentApiDefaultIdBehaviour(configFile);
        container.Install(Configuration.FromXml(new StaticContentResource(xd.OuterXml)));

    }

    private static XmlDocument immitateFluentApiDefaultIdBehaviour(string configFile)
    {
        var xd = new XmlDocument();
        xd.Load(configFile);

        foreach (
            XmlElement component in
                xd.SelectNodes("/configuration/components/component[@type and (not(@id) or @id = '')]"))
        {
            var type = Type.GetType(component.GetAttribute("type"), true);
            component.SetAttribute("id", type.FullName);
        }

        return xd;
    }

    private static IRegistration[] union(
        IList<Type> unnamed, IDictionary<string, Type> named, params Func<IEnumerable<IRegistration>>[] methods)
    {
        var all = new List<IRegistration>();
        foreach (var method in methods)
        {
            foreach (var registration in method())
            {
                var registrationType = registration.GetType();
                if (registrationType.IsGenericTypeOf(typeof(ComponentRegistration<>)))
                {
                    var componentType = registrationType.GetGenericArgumentsFor(typeof(ComponentRegistration<>))[0];

                    var name = (string)registrationType.GetProperty("Name").GetValue(registration, null);

                    if (name != null)
                    {
                        if (named.ContainsKey(name))
                        {
                            _log.Debug(
                                m => m("Skipped registering default named component {0}.", name));
                            continue;
                        }
                    }
                    else if (unnamed.Contains(componentType))
                    {
                        _log.Debug(
                            m => m("Skipped registering default component for type {0}.", componentType));
                        continue;
                    }

                    all.Add(registration);
                }
                else
                {
                    all.Add(registration);
                }
            }
        }

        return all.ToArray();
    }
}
Herbherbaceous answered 21/8, 2010 at 8:39 Comment(0)
H
15

You could very simply do this in the place you need to override the default implementation. This is an example from our integration tests. Both implementations are now registered but your code will use the default one, which is the one you've just registered. Works like a bomb and doesn't have any impact on the rest of the application:

        var sendMailStub = MockRepository.GenerateStub<ISendMail>();
        _container.Register(
            Component
                .For<ISendMail>()
                .Instance(sendMailStub)
                .IsDefault()
            );
Hegarty answered 30/5, 2013 at 9:15 Comment(1)
IsDefault API is not available in the windsor castle assemblies I've referred. I'm referring Castle.Core assembly version 2.5.1.0. Am I referring to an older assembly?Professional
A
10

I agree with Krzysztof that this is usually not a good idea... However as far as I can tell OverWrite() does not overwrite the default component, it just overwrites the lifestyle defined by attribute (i.e. [Singleton]).

If you want to replace a component, you can use container.Kernel.RemoveComponent(string key) followed by the registration of the new component.

Here's an example where this does make sense.

Aliment answered 20/3, 2009 at 0:25 Comment(7)
@George: can you elaborate on that? it should work as long as no other components depend on the one trying to be removed (which makes sense)Aliment
Ah I see, but how then to specify an alternate implementation? I don't want to remove the whole graph and re-add it.Angeliaangelic
@Mauricio: the problem is that RemoveComponent fails even if no components have been instantiated yet. Why enforce this rule during the building of the container, while it is by definition a "work in progress"?Funkhouser
@Igor: RemoveComponent is usually a bad idea, to the point that it's under consideration to be obsoleted/removed. On a related note, Krzysztof is planning to clearly separate building from resolving in a future version of Windsor, so RemoveComponent may make more sense then.Aliment
@Mauricio: good to hear about this. Another suggestion: how about adding ReplaceComponent() method or, even more simple: providing a method for changing the order of resolving components of the same service. So instead of calling RemoveComponent() + RegisterComponent, user could simply say something like RegisterComponentAsFirstImplementation()Funkhouser
@Igor: you can probably pull it off with an IHandlerSelector or custom naming subsystem, I'd argue against adding more public methods to the container due to bloat, however you may suggest it for v3! castle.uservoice.com/forums/38955-windsor-v3Aliment
.RemoveComponent() no longer exists in Windsor 3, but you can use IsDefault() to override the instance that was configured first.Emunctory
H
3

This would be the kind of problem that might be better solved by setting the container up with a decorator, where you can explicitly change the implementation the decorator is directing calls to ... especially if you want to replace the implementation that existing components (i.e. singletons) have been injected with which might exist for the lifetime of your application.

Really need more background on what you're trying to achieve.


Here is more information about registering Decorators with Windsor.

Ha answered 21/3, 2009 at 22:13 Comment(1)
link is 404 nowVeronique
S
3

I needed to switch a component implementation when running a system in a integration test suite and failed to use container.Kernel.RemoveComponent(). So I ended up with a simple facility which takes care of this for me.

Satirist answered 28/10, 2009 at 21:0 Comment(1)
unfortunately all overrides have to be specified before the registration is doneAngeliaangelic
H
1

I actually found a nice solution where I combine xml-files for overrides and use fluent registrations for defaults.

The fluent-API takes the fullname of a impl as the default key. On the fly I override the id's of the xml-config to imitate the key-conventions of the fluent-API.

Then i register the xml-config while I listen to Kernel.ComponentRegistered.

Afterwards I only add services from the code-config where the xml hasn't yet defined the service.

(it's a while ago and I just copy-pasted the code. hopefully you get it working. I'll do edits if you find any problems)

IList<Type> unnamedServices = new List<Type>();
IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

container.Kernel.ComponentRegistered += registered;

// The method that captures the services
private static ComponentDataDelegate captureRegistrations(
    IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
{
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                       namedServices.Add(key, handler.Service);
                   }
               };
}

After that before I register services in code, I check if they already have been registered. I also created a base class that makes this more easy. This is an application configuration:

public class ApplicationConfiguration : WindsorConfigurationSkeleton
{
    internal static WindsorServiceLocator create()
    {
        var container = createWith(null, "components-config.xml", coreServices, caches, roles);
        return new WindsorServiceLocator(container);
    }

    internal static IEnumerable<IRegistration> coreServices()
    {
        yield return Component.For<ISystemClock>()
            .ImplementedBy<PreciseSystemClock>()
            .Parameters(Parameter.ForKey("synchronizePeriodSeconds").Eq("10"))
            .LifeStyle.Singleton;

        yield return Component.For<IMailService>()
            .ImplementedBy<MailQueueService>()
            .LifeStyle.Singleton;
    }

    internal static IEnumerable<IRegistration> caches()
    {
        yield return Component.For<IDataCache<ServiceAttributes>>()
            .ImplementedBy<NoDataCache<ServiceAttributes>>()
            .LifeStyle.Singleton;

        // ....
    }
}

The base class that does the wiring: (Logging is from Commons.Logging)

public class WindsorConfigurationSkeleton
{
    private static readonly ILog _log = LogManager.GetLogger(
        typeof(WindsorConfigurationSkeleton));

    internal static IWindsorContainer createWith(
        IRegistration[] customs, string configFile, params Func<IEnumerable<IRegistration>>[] methods)
    {
        IWindsorContainer container = new WindsorContainer();
        BugFix.Kernel = container.Kernel;

        container.AddFacility("factory.support", new FactorySupportFacility());

        IList<Type> unnamedServices = new List<Type>();
        IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

        ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

        container.Kernel.ComponentRegistered += registered;

        if (customs != null)
        {
            container.Register(customs);
        }

        if (configFile != null)
        {
            tryAddXmlConfig(container, configFile);
        }

        container.Kernel.ComponentRegistered -= registered;

        if (methods != null && methods.Length > 0)
        {
            container.Register(union(unnamedServices, namedServices, methods));
        }

        return container;
    }

    private static ComponentDataDelegate captureRegistrations(
        IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
    {
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                        var text = unnamedServices.Contains(handler.Service) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {2} service for {0} with {1}.",
                                    handler.Service.GetDisplayName(),
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));

                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                        var text = namedServices.ContainsKey(key) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {3} service {0} with name '{1}' and {2}.",
                                    handler.ComponentModel.Service,
                                    handler.ComponentModel.Name,
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));
                       namedServices.Add(key, handler.Service);
                   }
               };
    }

    protected static void tryAddXmlConfig(IWindsorContainer container, string filename)
    {
        var fi = Resources.GetFileFromResourceHierarchy(typeof(ApplicationContext).Namespace, filename);
        if ( fi == null ) {
            return;
        }
        var configFile = fi.FullName;
        var xd = immitateFluentApiDefaultIdBehaviour(configFile);
        container.Install(Configuration.FromXml(new StaticContentResource(xd.OuterXml)));

    }

    private static XmlDocument immitateFluentApiDefaultIdBehaviour(string configFile)
    {
        var xd = new XmlDocument();
        xd.Load(configFile);

        foreach (
            XmlElement component in
                xd.SelectNodes("/configuration/components/component[@type and (not(@id) or @id = '')]"))
        {
            var type = Type.GetType(component.GetAttribute("type"), true);
            component.SetAttribute("id", type.FullName);
        }

        return xd;
    }

    private static IRegistration[] union(
        IList<Type> unnamed, IDictionary<string, Type> named, params Func<IEnumerable<IRegistration>>[] methods)
    {
        var all = new List<IRegistration>();
        foreach (var method in methods)
        {
            foreach (var registration in method())
            {
                var registrationType = registration.GetType();
                if (registrationType.IsGenericTypeOf(typeof(ComponentRegistration<>)))
                {
                    var componentType = registrationType.GetGenericArgumentsFor(typeof(ComponentRegistration<>))[0];

                    var name = (string)registrationType.GetProperty("Name").GetValue(registration, null);

                    if (name != null)
                    {
                        if (named.ContainsKey(name))
                        {
                            _log.Debug(
                                m => m("Skipped registering default named component {0}.", name));
                            continue;
                        }
                    }
                    else if (unnamed.Contains(componentType))
                    {
                        _log.Debug(
                            m => m("Skipped registering default component for type {0}.", componentType));
                        continue;
                    }

                    all.Add(registration);
                }
                else
                {
                    all.Add(registration);
                }
            }
        }

        return all.ToArray();
    }
}
Herbherbaceous answered 21/8, 2010 at 8:39 Comment(0)
E
0

Yes, it does redefine default implementation of a service. Why do you do this? Why not register this in the first place, instead of redefining it?

Could you provide more context?

Exchangeable answered 19/3, 2009 at 12:31 Comment(3)
I have a xml-file that overrides the fluent wirings. But I solved it now by reading the xml file first and not adding the configurations from the code, when the xml-file redefines them.Herbherbaceous
@Lars: Can you be more specific how you do that? I have the same problem here, I have my core wirings and want to switch one implementation with another WITHOUT have to modify the core wirings.Receivable
@KrzysztofKozmic - I am an avid user of Windsor. In my BDD tests I wire up my services using as much real code as I can and use all the IoC code to do the wire up however there are times that I want to replace a registration with a mocked type and I want to do this with as little change to production code as possible,Colorblind

© 2022 - 2024 — McMap. All rights reserved.