Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject: Injecting two different objects of same type

Tags:

c#

ninject

If I have a class, which has dual dependencies on the same type (needs two different instances), if the only difference between the instances is a deeper dependency, what's the best way to have Ninject perform DI and keep the two graphs separate?

Example object graph:

foo  ClassA  ClassB
bar  ClassA  ClassB

Class C's constructor:

public class C
{
    public C(ClassB foo, ClassB bar) {  }
}

So how do I make sure that the ClassB instantiated with foo gets supplied as the ClassB dependancy foo, and barbar?

Just for some background, I had some requirements change, so I need to replace a write-only repository (IAdd) with a composite write-only repository

public class CompositeWriteRepository<T> : IAdd<T>
{
    public CompositeWriteRepository(IAdd<T> foo, IAdd<T> bar, Func<T, bool> descriminator) { ... }
    public Add(T entity)
    {
        if (descriminator(entity)) {
            foo.Add(entity);
        } else {
            bar.Add(entity);
        }
    }        
}

With mocking, that was easy enough, I could just inject using names:

 kernel.Bind<IAdd<EntityType>>().To<fooRepository>().Named("foo");
 kernel.Bind<IAdd<EntityType>>().To<barRepository>().Named("bar");

 kernel.Bind<IAdd<EntityType>>().To<CompositeWriterRepository<EntityType>>()
    .WithConstructorArgument("foo", x => x.Kernel.Get<IAdd<EntityType>>("foo")
    .WithConstructorArgument("bar", x => x.Kernel.Get<IAdd<EntityType>>("bar");

The problem comes when I use the real repositories; foo and bar ultimately write to files so they need different file names. Since they're StreamWriter repositories, one of their dependencies actually gets the two different file names.

string FileName  FileStreamWriterFactory  StreamRepository  CompositeRepository

The only way I've found thus far to construct things is to make a named FileName, named FileStreamWriterFactory, named StreamRepository × 2 (once for foo and once for bar). This seems like a lot of work, so I hope there's a better solution.

I can re-architect if needed, it felt like an elegant way to quickly add in seperating the entries when the requirements changed. I realize my classes all are quite specific, but I think single responsibility would support this generally and in my case we write a bunch of small applications and have more requests the we can fulfill so its a good way to have lot's of re-usable code laying around that I basically just need to re-configure for different tasks.

Solution
Remo Gloor should get credit; his is probably the best practice.

What I actually did was create a new extension

public static bool WhenAnchester(this IRequest request, Func<IRequest, bool> conditions)
{
    var parentContext = request.ParentContext;
    if (parentContext == null) {
            return false;
    }

    return conditions(parentContext.Request) || 
        parentContext.Request.WhenAnchester(conditions);
} 

This then let me easily control which file get injected into which repository.

kernel.Bind<string>().ToConstant("Foo.txt")
    .When(x => x.Target.Name == "filepath" && 
              x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest1"));

kernel.Bind<string>().ToConstant("Bar.txt")
    .When(x => x.Target.Name == "filepath" &&
               x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest2"));

There's probably a better solution, so I wouldn't necessary recommend this for others, but its working well for me.

like image 969
W3t Tr3y Avatar asked Feb 22 '12 22:02

W3t Tr3y


1 Answers

Yes there is a better way

See https://github.com/ninject/ninject/commit/60443badf4ef840531c93e9287b154a9bba337c2

It's 3.0 but IsAnyAnchestorNamed can also be used with 2.2 from a When condition.

like image 163
Remo Gloor Avatar answered Nov 05 '22 18:11

Remo Gloor