8

I am just beginning to learn about IoC and Dependency Injection. I am planning on doing a MonoTouch project and wanted to use TinyIoC but I wanted to test it out first. I'm creating a dummy credit card processing console app and I'm having trouble with how to configure TinyIoC since I have multiple implementations of my interface. This is my test app.

Interface:

public interface IPaymentProcessor
{
    void ProcessPayment(string cardNumber);
}

Two Implementations of the interface:

VisaPaymentProcessor

public class VisaPaymentProcessor : IPaymentProcessor
{
    public void ProcessPayment(string cardNumber)
    {
        if (cardNumber.Length != 13 && cardNumber.Length != 16)
        {
            new ArgumentException("Card Number isn't the correct length");
        }

        // some code for processing payment
    }
}

AmexPaymentProcessor

public class AmexPaymentProcessor : IPaymentProcessor
{
    public void ProcessPayment(string cardNumber)
    {
        if (cardNumber.Length != 15)
        {
            new ArgumentException("Card Number isn't the correct length");
        }

        // some code for processing the payment
    }
}

Simple stuff. Now I have a class that accepts the interface as a parameter in the constructor....

CreditCardProcessor

public class CreditCardProcessor
{
    public IPaymentProcessor PaymentProcessor { get; set; }

    public CreditCardProcessor(IPaymentProcessor processor)
    {
        this.PaymentProcessor = processor;
    }

    public void ProcessPayment(string creditCardNumber)
    {
        this.PaymentProcessor.ProcessPayment(creditCardNumber);
    }
}

My console app looks like this....

class Program
{
    static void Main(string[] args)
    {
        TinyIoCContainer.Current.AutoRegister();

        var creditCardProcessor = TinyIoCContainer.Current.Resolve<CreditCardProcessor>();
        creditCardProcessor.ProcessPayment("1234567890123456"); // 16 digits
    }
}

So I am trying to figure out how to tell the Resolve which implementation of the interface to pass to the constructor. If I run this code, I will always use the VisaPaymentProcessor implementation.

So how can I make TinyIoC pass the AmexPaymentProcessor implementation to the constructor rather than the VisaPaymentProcessor(which seems to be the default)?

Ryan Alford
  • 7,514
  • 6
  • 42
  • 56

3 Answers3

7

I haven't used TinyIoC myself, but I suspect you want:

TinyIoCContainer.Current.Register(typeof(IPaymentProcessor),
                                  typeof(AmexPaymentProcessor));

(If you want to use Amex.)

There are various other Register overloads available, including one which takes a name to use, which may be useful when you resolve. It really depends on what you're trying to achieve, which isn't terribly clear from the question.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks. I updated the post with the question. It seems that `VisaPaymentProcessor` is the "default" implementation that TinyIoC uses. How can I get TinyIoC to pass the `AmexPaymentProcessor` implementation to the constructor instead. Sorry about not being clear. – Ryan Alford May 02 '12 at 21:08
  • @Eclipsed4utoo: Okay - in that case, I'd expect my answer to cover it :) – Jon Skeet May 02 '12 at 21:13
  • @JonSkeet apologies for my lack of understanding, but how does this address the OPs original problem? It looks like they want to be able to conditionally resolve to the registered dependencies at runtime. – tom redfern Jun 13 '15 at 21:39
  • @Tom: I don't see anything about determining it at execution time in the question. – Jon Skeet Jun 13 '15 at 21:43
  • @JonSkeet isn't that what they're asking by saying *So I am trying to figure out how to tell the Resolve which implementation of the interface to pass to the constructor* ? The constructor in this case being `public CreditCardProcessor(IPaymentProcessor processor)` – tom redfern Jun 13 '15 at 21:51
  • @Tom: I don't think so - but given that the OP didn't come back with more comments, it's hard to say (and a long time ago). The OP just said they wanted to use the Amex one - nothing about sometimes using Amex and sometimes Visa. – Jon Skeet Jun 13 '15 at 22:05
2

I'm not really sure what you're trying to achieve here, but if you have multiple implementations of an interface and you want a specific one then you need to register each one with a name, or use RegisterMultiple, which uses the type name for a name, then resolve using that name and use that along with NamedParameterOverloads to specify which one you want.

It sounds more like though that you might want some kind of ProcessorFactory, or a facade of some kind, that takes a dependency on IEnumerable and supplies/acts as a facade for the correct implementation depending on the number passed in.

Steven Robbins
  • 26,441
  • 7
  • 76
  • 90
  • 1
    I read this about Windsor IoC and was wondering if the same is true for TinyIoC.... `Now when you "Resolve" an instance of CreditCardProcessor, the container looks at the constructor and sees that it needs an IPaymentProcessor. The container looks to see if that type is registered in the container. If it is, the container will "Resolve" that type and then instantiate CreditCardProcessor.` If so, how does TinyIoC handle when there are two implementations registered for the interface? It seems to choose the first registered type when using `RegisterMultiple`. – Ryan Alford May 02 '12 at 21:28
  • 1
    @Eclipsed4utoo There's only ever one "default" registration for an interface (one with no name), and that's the one that you get if you take a dependency on it. When you use AutoRegister which one gets to be the "default" one isn't guaranteed so you should set it yourself, or register them all and pull the correct one out by name. – Steven Robbins May 03 '12 at 06:12
  • Thanks Steven. The only way I can find to specify the correct implementation is to use the NamedParameterOverloads when resolving the `CreditCardProcessor`. However, that would seem to "hardcode" the constructor's signature as I would need the NamedParametersOverload to match the constructor signature. If the constructor changes, the NamedParametersOverload will now fail if I don't also make the changes there. To me, that kind of defeats the purpose of using an IoC. Am I just way off base? – Ryan Alford May 03 '12 at 10:40
2

Something like this in Global.asax or application entry (Modified for your example)

        const string nameTrim = "paymentprocessor"; 
        var type = typeof(IPaymentProcessor);
        AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList()
            .ForEach(t =>
            {
                var name = t.Name.ToLower();
                if (name.EndsWith(nameTrim))
                    name = name.Substring(0, name.Length - nameTrim.Length);

                TinyIoCContainer.Current.Register(type, t, name);
            });

It finds alla implementations of IPaymentProcessor and registers them with classname (-PaymentProcessor, if the classname ends with PaymentProcessor)

Then I can resolve for example "AmexPaymentProcessor" with

        IPaymentProcessor handler;
        if (TinyIoCContainer.Current.TryResolve("amex", out handler))
        {
            response = handler.ProcessPayment(cardNumber);
        }