1

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();
    }
}
Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • Injecting a `Func` is one way to get the job done. Using a [strategy pattern](https://stackoverflow.com/a/31971691/) is another. This would be a cleaner solution to having the "list" on hand. That said, it isn't really clear what issue you are trying to solve - is it getting the "default" operation from the list? – NightOwl888 Oct 12 '17 at 03:00
  • @NightOwl888 Issue is multiple bindings for same type will be handled by `Func`, other one `IEnumerable` is not used, its required only for one to one / unique / unnamed binding, but still i need a collection, otherwise for named bindings lead to exception, they are unnecessarily filled. Please run the code and try `T bankingOperation` instead of `IEnumerable bankingOperationList` in `BankTran`, you will get the issue I am talking about – Mrinal Kamboj Oct 12 '17 at 07:43
  • @NightOwl888 I reviewed the solution that you provided, which obviate the need of `Func`, by `ICarFactory[]` itself finding the correct object, but that way I am able to make the current solution work elegantly, I just wanted to avoid named binding requirement to fill in a collection when is doing its job, but Ninject remains clue less in that case and wants collection to fill – Mrinal Kamboj Oct 12 '17 at 08:14

1 Answers1

1

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");
    }
}
jbl
  • 15,179
  • 3
  • 34
  • 101
  • Thanks for the details, check out the edit in the question marked as **Edit 1, based on response by jbl** Your code helped me in running a part as marked in main method without an explicit Name, but it doesn't still helps in running a version with Name like "A", which is my primary challenge – Mrinal Kamboj Oct 12 '17 at 16:15
  • @MrinalKamboj shouldn't the not working line in your edited example be `var transaction = kernel.Get>("A");` ? – jbl Oct 12 '17 at 19:46
  • That's hat I have marked as non working, just that I have included that part in the code, so that you can run and review the error. Other part Without "A" is working, as suggested in the comment above. – Mrinal Kamboj Oct 13 '17 at 00:43
  • @MrinalKamboj : ok I think I understood. I edited my answer with a running example. – jbl Oct 13 '17 at 10:59