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

Using dynamic dependencies with components for the same service #112

Open
s-tarasov opened this issue Dec 15, 2015 · 5 comments
Open

Using dynamic dependencies with components for the same service #112

s-tarasov opened this issue Dec 15, 2015 · 5 comments

Comments

@s-tarasov
Copy link

class Program
    {
        public interface ITestRegistration
        {

        }

        public class TestRegistration : ITestRegistration
        {
            private readonly Uri _uri;

            public TestRegistration(Uri uri)
            {
                _uri = uri;
            }
        }


        public class TestRegistrationDecorator : ITestRegistration
        {
            private readonly ITestRegistration _underlying;

            public TestRegistrationDecorator(ITestRegistration underlying)
            {
                _underlying = underlying;
            }
        }


        static void Main(string[] args)
        {
            var container = new WindsorContainer();

            container.Register(
                Component
                    .For<ITestRegistration>()
                    .ImplementedBy<TestRegistrationDecorator>()
                    .Named("component2"),
                 Component
                    .For<ITestRegistration>()
                    .ImplementedBy<TestRegistration>()
                    .DependsOn((k, p) =>
                    {
                        p["uri"] = new Uri("http://test.com");

                    })
                    .Named("component1")
            );


            var component1 = container.Resolve<ITestRegistration>("component1");

            var component2 = container.Resolve<ITestRegistration>("component2");
        }
    }

this code thrown on resolve component 2 with message:

An unhandled exception of type 'Castle.MicroKernel.Handlers.HandlerException' occurred in Castle.Windsor.dll

Additional information: Can't create component 'component2' as it has dependencies to be satisfied.

'component2' is waiting for the following dependencies:

  • Service 'WindsorDynamicParametersProblem.Program+ITestRegistration' which points back to the component itself.

A dependency cannot be satisfied by the component itself, did you forget to make this a service override and point explicitly to a different component exposing this service?

The following components also expose the service, but none of them can be resolved:

'component1' is waiting for the following dependencies:

  • Service 'System.Uri' which was not registered.
@s-tarasov s-tarasov changed the title using dynamic dependencies with components for the same service Using dynamic dependencies with components for the same service Dec 15, 2015
@IanYates
Copy link

Reverse the order of your registrations, that is, register component1 then component2.

For example, see https://nexussharp.wordpress.com/2012/04/22/castle-windsor-how-to-register-components/

Also see http://mikehadlow.blogspot.com.au/2010/01/10-advanced-windsor-tricks-4-how-to.html, although it suggests the ordering you had... Hmm 😕 I don't have my "dependency injection in .NET" book handy :)

@s-tarasov
Copy link
Author

@IanYates see https://github.com/castleproject/Windsor/blob/master/docs/registering-components-one-by-one.md#register-more-components-for-the-same-service

In Windsor first one wins

It works perfectly for not dynamic parameters. For example:

Component
                    .For<ITestRegistration>()
                    .ImplementedBy<TestRegistration>()
                    .DependsOn(Dependency.OnValue("uri", new Uri("http://test.com"))
                    .Named("component1")

@IanYates
Copy link

In Windsor first one wins

True. But that page doesn't discuss decorators - at least not directly.

When you try to resolve component2, Windsor tries to resolve its dependency, which is an instance of ITestRegistration. First one wins means it comes to component2 again, which it can't create.

So registering component1 first solves you problem.
Alternatively keep your registration order but indicate that component1 is your default like so

         Component
            .For<ITestRegistration>()
            .ImplementedBy<TestRegistration>()
            .DependsOn((kernel, paramDict) =>
            {
                paramDict["uri"] = new Uri("http://test.com");

            })
            .Named("component1")
            .IsDefault()

Alternatively you can keep your registration order but instruct Windsor that component2 really needs component1 by using service overrides like

        Component
            .For<ITestRegistration>()
            .ImplementedBy<TestRegistrationDecorator>()
            .ServiceOverrides(ServiceOverride.ForKey("underlying").Eq("component1"))
            .Named("component2"),
         Component
            .For<ITestRegistration>()
            .ImplementedBy<TestRegistration>()
            .DependsOn((kernel, paramDict) =>
            {
                paramDict["uri"] = new Uri("http://test.com");

            })
            .Named("component1")

Now that still doesn't work (and ServiceOverrides, despite being in the docs at https://github.com/castleproject/Windsor/blob/master/docs/registering-components-one-by-one.md#supplying-the-component-for-a-dependency-to-use-service-override, is deprecated according to my LinqPad intellisense).

This changes the error message slightly and made the issue more obvious to me

It was previously

Can't create component 'component2' as it has dependencies to be satisfied.

'component2' is waiting for the following dependencies:
- Service 'UserQuery+ITestRegistration' which points back to the component itself.
A dependency cannot be satisfied by the component itself, did you forget to make this a service override and point explicitly to a different component exposing this service?

The following components also expose the service, but none of them can be resolved:
'component1' is waiting for the following dependencies:
- Service 'System.Uri' which was not registered.

And with the ServiceOverride it's now

Can't create component 'component2' as it has dependencies to be satisfied.

'component2' is waiting for the following dependencies:
- Component 'component1' (via override) which was registered but is also waiting for dependencies.

'component1' is waiting for the following dependencies:
- Service 'System.Uri' which was not registered.

Note that in the first case Windsor is trying to use component2 to satisfy the dependency and, as a hint, happens to mention there's also this component1 thing but it's also got a problem (the Uri). At least in the second case it's a bit more deterministic.

Your point about the specification of the uri parameter is interesting. I suspect registration happens to work in any order at the moment because Windsor is trying smart fallback (it wouldn't make sense to use component2 to give a dependency to itself but if you had component1, component2 and component3 that's not so easy to resolve) but that then fails due to a bug with DependsOn.

This is all pointing to a bug IMHO with the DependsOn overload that accepts parameters via a dictionary, but that bug isn't hit if the registrations are in a different order or if one is specified as the default.

I haven't given you a fix or even a good explanation but I hope there's something there of use :) If one of the core Castle devs doesn't pick this up I might step through the code over the Christmas break to see if I can spot what's going wrong and make my first PR to Windsor.

@s-tarasov
Copy link
Author

By the way, my code snippet works if I add TypedFactoryFacility registration.

@General-Fault
Copy link

General-Fault commented Apr 3, 2017

If "component2" depends on a named component, the dynamic DependsOn are ignored and the creation fails. This means that you cannot register a decorator that decorates different instances of the same service. For instance if you have class BogusClass that implements BogusInterface, and you have multiple instances of BogusClass with different constructor properties specified in a dynamic DependsOn, you cannot have a decorator.

[Test]
public void TestDecoratorWithNamedBaseAndDynamicProperties()
{
    var container = new WindsorContainer();

    container.Register(
        Component.For<IBogusInterface>()
            .Named("groucho.base")
            .ImplementedBy<BogusClass>()
            .DependsOn((kernel, parameters) =>
            {
                parameters["bogusName"] = "groucho";
                parameters["bogosityIndex"] = 111;
            }),
        Component.For<IBogusInterface>()
            .Named("harpo.base")
            .ImplementedBy<BogusClass>()
            .DependsOn((kernel, parameters) =>
            {
                parameters["bogusName"] = "harpo";
                parameters["bogosityIndex"] = 222;
            }),

        Component.For<IBogusInterface>()
            .ImplementedBy<BogusDecorator>()
            .Named("groucho")
            .DependsOn(Dependency.OnComponent(typeof(IBogusInterface), "groucho.base")),

        Component.For<IBogusInterface>()
            .ImplementedBy<BogusDecorator>()
            .Named("harpo")
            .DependsOn(Dependency.OnComponent(typeof(IBogusInterface), "harpo.base"))
    );

    var groucho = container.Resolve<IBogusInterface>("groucho");
    var harpo = container.Resolve<IBogusInterface>("harpo");

    Assert.That(groucho.BogusName, Is.EqualTo("groucho"));
    Assert.That(harpo.BogusName, Is.EqualTo("harpo"));
}

For completeness:

public interface IBogusInterface
{
    string BogusName { get;}
    int BogosityIndex { get;}
}

public class BogusClass : IBogusInterface
{
    public BogusClass(string bogusName, int bogosityIndex)
    {
        BogusName = bogusName;
        BogosityIndex = bogosityIndex;
    }

    public string BogusName { get; }
    public int BogosityIndex { get; }
}

public class BogusDecorator : IBogusInterface
{
    private readonly IBogusInterface _target;

    public BogusDecorator(IBogusInterface target)
    {
        _target = target;
    }

    public string BogusName => _target.BogusName;
    public int BogosityIndex => _target.BogosityIndex;
}

One workaround is to use UseFactoryMethod such as:

container.Register(
    Component.For<IBogusInterface>()
        .Named("groucho.base")
        .UsingFactoryMethod(kernel => new BogusClass("groucho", 111)),
    Component.For<IBogusInterface>()
        .Named("harpo.base")
        .UsingFactoryMethod(kernel => new BogusClass("harpo", 222)),

    Component.For<IBogusInterface>()
        .ImplementedBy<BogusDecorator>()
        .Named("groucho")
        .DependsOn(Dependency.OnComponent(typeof(IBogusInterface), "groucho.base")),

    Component.For<IBogusInterface>()
        .ImplementedBy<BogusDecorator>()
        .Named("harpo")
        .DependsOn(Dependency.OnComponent(typeof(IBogusInterface), "harpo.base"))
);

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

3 participants