Mrinal Kamboj
Mrinal Kamboj

Reputation: 11478

Ninject how to avoid array injection for multiple name bindings

Review the following code, where to take care of cases with both single and named binding for an interface, an abstract factory is used as suggested here

Parameterized Factories Using Ninject

Challenge here is, I need to introduce IEnumerable<T> bankingOperationList, instead of T bankingOperationList, since for named binding it will always use the abstract factory injection, Func<string,T> bankingOperationFunc, but if I don't use IEnumerable<T> suggested above it leads to exception, due to this for even non named single binding, I need to use something like: bankingOperationList.FirstOrDefault().Withdraw(), even when I know there will only be one dependency. Another challenge is, for some named bindings it has 30 - 40 bindings in few cases, which will be unnecessarily filled, when I can default T bankingOperationList to null, as it is not required. Please let me know, if the issue needs further clarification. Working Console project underneath.

public interface IBankingOperation
{
    void Withdraw();
}

public class BankingOperationOne : IBankingOperation
{
    public BankingOperationOne()
    {
        Console.WriteLine("Testing Constructor :: One :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation One");
    }
}

public class BankingOperationTwo : IBankingOperation
{
    public BankingOperationTwo() 
    {
        Console.WriteLine("Testing Constructor :: Two :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation Two");
    }
}

// Ninject Bindings
public class Bindings : NinjectModule
{
    public override void Load()
    {
        Bind<IBankingOperation>().To<BankingOperationOne>()
                                 .Named("A");

        Bind<IBankingOperation>().To<BankingOperationTwo>()
                                 .Named("B");

        Bind<Func<string,IBankingOperation>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation>(name));
    }
}

public class BankTran<T> where T : IBankingOperation
{
    private IEnumerable<T> bankingOperationList = null;

    private Func<string,T> bankingOperationFunc;

    public BankTran(IEnumerable<T> boList = null, 
                    Func<string,T> boFunc = null)
    {
        bankingOperationList = boList;
        bankingOperationFunc = boFunc;
    }

    public void DoOperation(string identifier = null)
    {
        if(bankingOperationFunc != null)        
            bankingOperationFunc(identifier).Withdraw();
        else
            bankingOperationList.FirstOrDefault().Withdraw();       

        Console.WriteLine("Transaction Successful ");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel();

        kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)

        var transaction = kernel.Get<BankTran<IBankingOperation>>();

        transaction.DoOperation("A");
    }
}

Edit 1, based on response by jbl

public interface IBankingOperation<T>
{
    void Withdraw();
}

public class BankingOperationOne : IBankingOperation<TestOne>
{
    public BankingOperationOne()
    {
        Console.WriteLine("Testing Constructor :: One :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation One");
    }
}

public class BankingOperationTwo : IBankingOperation<TestTwo>
{
    public BankingOperationTwo()
    {
        Console.WriteLine("Testing Constructor :: Two :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation Two");
    }
}

public class TestOne { }

public class TestTwo { }

// Ninject Bindings
public class Bindings : NinjectModule
{
    public override void Load()
    {

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));

        Bind<Func<string, IBankingOperation<TestOne>>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation<TestOne>>(name));

        Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
    }
}

public class BankTran<T> where T : class
{
    private IBankingOperation<T> bankingOperation;

    private Func<string, IBankingOperation<T>> bankingOperationFunc;

    public BankTran(IBankingOperation<T> bo = null,
                    Func<string, IBankingOperation<T>> boFunc = null)
    {
        bankingOperation = bo;
        bankingOperationFunc = boFunc;
    }

    public void DoOperation(string identifier = null)
    {
        if (bankingOperationFunc != null && identifier != null)
            bankingOperationFunc(identifier).Withdraw();
        else if (bankingOperation != null)
            bankingOperation.Withdraw();

        Console.WriteLine("Transaction Successful ");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true});

        kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)

        var transaction = kernel.Get<BankTran<TestOne>>("A"); // Not Working 

        // var transaction = kernel.Get<BankTran<TestOne>>(); // Working 

        transaction.DoOperation();
    }
}

Upvotes: 1

Views: 361

Answers (1)

jbl
jbl

Reputation: 15413

Assuming BankingOperationOne is your default behaviour, adding the following line in your Load method should allow to replace the IEnumerable<T> with T in your BankTran constructor :

Bind<IBankingOperation>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<>));

Another solution would be to just define a named binding for default behaviour

Bind<IBankingOperation>().To<BankingOperationOne>().Named("__DefaultBehaviour");

then

 public void DoOperation(string identifier = "__DefaultBehaviour")
        {
            if (bankingOperationFunc != null)
                bankingOperationFunc(identifier).Withdraw();

            Console.WriteLine("Transaction Successful ");
        }

Edit :

You should use the Ninject.Extenstions.Factory nuget package. Using this package, the following code seems to fullfill you requirements.

public interface IBankingOperation<T>
{
    void Withdraw();
}

public interface IBankingOperationFactory<T>
{
    IBankingOperation<T> GetBankingOperation(string name);
}

public class BankingOperationOne : IBankingOperation<TestOne>
{
    public BankingOperationOne()
    {
        Console.WriteLine("Testing Constructor :: One :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation One");
    }
}

public class BankingOperationTwo : IBankingOperation<TestTwo>
{
    public BankingOperationTwo()
    {
        Console.WriteLine("Testing Constructor :: Two :: Empty");
    }

    public void Withdraw()
    {
        Console.WriteLine("Money Withdrawl Operation Two");
    }
}

public class TestOne { }

public class TestTwo { }

// Ninject Bindings
public class Bindings : NinjectModule
{
    public override void Load()
    {

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");

        Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));

        Bind<IBankingOperationFactory<IBankingOperation<TestOne>>>().ToFactory();

        Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
    }
}

public class BankTran<T> where T : class
{
    private IBankingOperation<T> bankingOperation;

    private IBankingOperationFactory<T> _bankingOperationFactory;

    public BankTran(IBankingOperation<T> bo = null,
                    IBankingOperationFactory<T> bankingOperationFactory = null)
    {
        bankingOperation = bo;
        _bankingOperationFactory = bankingOperationFactory;
    }

    public void DoOperation(string identifier = null)
    {
        if (_bankingOperationFactory != null && identifier != null)
            _bankingOperationFactory.GetBankingOperation(identifier).Withdraw();
        else if (bankingOperation != null)
            bankingOperation.Withdraw();

        Console.WriteLine("Transaction Successful ");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true });

        kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)

        var transaction = kernel.Get<BankTran<TestOne>>();

        transaction.DoOperation();
        transaction.DoOperation("A");
        transaction.DoOperation("B");
    }
}

Upvotes: 1

Related Questions