Cyclic dependency with ninject
Asked Answered
A

3

12

I'm trying to figure out correct way how to bind something like this with ninject.

interface IMainService
{
    void DoStuff();
}

interface IOtherService
{
    void DoSomeMagic();
}

abstract class BaseClass
{
    //many stuff here
}

class MainClass : BaseClass, IMainService
{
    public MainClass(IOtherService s)
    {
    }

    public void DoStuff()
    {
        throw new NotImplementedException();
    }

    //do many other things
}

class OtherClass : IOtherService
{
    public OtherClass(IMainService s)
    {
    }

    public void DoSomeMagic()
    {
        throw new NotImplementedException();
    }
}

class BaseModule : NinjectModule
{
    public override void Load()
    {
        Bind<MainClass>().To<MainClass>();
        Bind<IMainService>().To<MainClass>();
        Bind<IOtherService>().To<OtherClass>();
    }
}

static class Program
{
    static void Main()
    {
        var kernel = new StandardKernel(new BaseModule());
        var main = kernel.Get<MainClass>();
    }
}

It gives me exception:

Error activating IOtherService using binding from IOtherService to OtherClass
A cyclical dependency was detected between the constructors of two services.

Activation path:
  4) Injection of dependency IOtherService into parameter s of constructor of type MainClass
  3) Injection of dependency IMainService into parameter s of constructor of type OtherClass
  2) Injection of dependency IOtherService into parameter s of constructor of type MainClass
  1) Request for MainClass

Suggestions:
  1) Ensure that you have not declared a dependency for IOtherService on any implementations of the service.
  2) Consider combining the services into a single one to remove the cycle.
  3) Use property injection instead of constructor injection, and implement IInitializable if you need initialization logic to be run after property values have been injected.

I don't know how to write BaseModule. I need only one instance of MainClass and one instance of OtherClass (like singletons).

I tried things like that:

Bind<MainClass>().To<MainClass>().InSingletonScope();
Bind<IMainService>().To<MainClass>().InRequestScope();
Bind<IOtherService>().To<OtherClass>().InSingletonScope();

But with same error.

And how to write binding for using only one instance for MainClass and IMainService interfaces?

Thanks for answers.

Anglim answered 18/8, 2010 at 11:29 Comment(0)
S
16

As the error message says, you have a cyclic dependency between MainClass and OtherClass since you can't create one without an instance of the other. Ideally, you should restructure your class hierarchy to remove this requirement.

If you can't, the solution is to use property injection for one (or both) of the classes, e.g.

public interface IMainService
{
    void DoStuff();
    IOtherService OtherService { set; }
}

public class MainClass
{
    public IOtherService OtherService { get; set; }
    public void DoStuff() { ... }
}

public class OtherService
{
    public OtherService(IMainService main)
    {
        main.OtherService = this;
    }
}
Sommer answered 18/8, 2010 at 12:11 Comment(3)
Thanks for this tip. I found perfect solution with property injection. But it is without IOtherService OtherService { set; } on IMainServices, becouse when i decorate property with [Inject], Ninject add correct instance to it by itself.Anglim
This does not work. With the latest version of Ninject, it will throw a StackOverflowException if you use property injection for both, and will throw the "cyclical dependency was detected" if only one is using property injection (and the other constructor injection).Frankfrankalmoign
Ah, but it does work as long as you're using a scope other than transient scope (the default).Frankfrankalmoign
S
7

I think you should not use properties or setter methods for this, you'd better use Lazyness. The concept of lazyness resolves the problem. The problem is that if you have circular dependency between objects it becomes unclear what to create a first. Lazyness resolves is: once a the object is really used (in general is this the case when a public method is called, it needs to exist). Please avoid properties or setters if you possibly can. They make your object mutable (bad for thread safety and unneeded when the dependency should be injected only once).

You constructors shall look like this:

public OtherService(Lazy<IMainService> main)
{
    this.main = main;
}

public MainClass(Lazy<IOtherService> s)
{
    this.s = s;
}

You can describe these lazy dependencies in you Ninject module using the Load method by calling "ToMethod("lambda method that creates the Lazy method based on the get method").

A clear example on how lazyness can solve circular dependencies with Ninject is presented here. It also describes a helper method (BindLazy) to solve your problem. https://www.codeproject.com/Tips/1171940/How-Ninject-Can-Help-in-Resolving-Circular-Depende

Selina answered 22/2, 2017 at 11:21 Comment(2)
I used this. This was pretty neat!Dado
Thank you for pointing out that this can be done using Lazy<T>; it is a great idea. Particularly for Ninject, however, I should point out that there are some ready-made extensions that do all the work of injecting for you: github.com/ninject/Ninject.Extensions.Factory/wiki/LazyOligopsony
B
0

Also you can solve it using improved Properties to avoid not desired access to setters:

public interface IMainService
{
    void DoStuff();
}

public class MainClass
{
    [Inject] public IOtherService OtherService { get; init; }
    public void DoStuff() { ... }
}

public class OtherService
{
    public OtherService(IMainService main)
    {
        main.OtherService = this;
    }
}
Blackcock answered 24/8, 2021 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.