Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DI with Unity when multiple instances of the same type is needed

I need help with this. I'm using Unity as my container and I want to inject two different instances of the same type into my constructor.

class Example
{
   Example(IQueue receiveQueue, IQueue sendQueue) {}
}

....and IQueue is implemented in my MessageQueue class....

class MessageQueue : IQueue
{
    MessageQueue(string path) {}
}

How can I inject two different instances of MessageQueue into my Example class? Each of the MessageQueue instances to be created with different path.

like image 920
Erik Z Avatar asked Aug 27 '13 05:08

Erik Z


5 Answers

There are many ways to achieve the results you want (as evidenced by the multiple answers). Here is another way using named registrations (without attributes):

IUnityContainer container = new UnityContainer();

container.RegisterType<IQueue, MessageQueue>("ReceiveQueue", 
    new InjectionConstructor("receivePath"));

container.RegisterType<IQueue, MessageQueue>("SendQueue",
    new InjectionConstructor("sendPath"));

container.RegisterType<Example>(
    new InjectionConstructor(
        new ResolvedParameter<IQueue>("ReceiveQueue"),
        new ResolvedParameter<IQueue>("SendQueue")));

Example example = container.Resolve<Example>();

The downside of this approach is that if the Example constructor is changed then the registration code must also be modified to match. Also, the error would be a runtime error and not a more preferable compile time error.

You could combine the above with an InjectionFactory to invoke the constructor manually to give compile time checking:

IUnityContainer container = new UnityContainer();

container.RegisterType<IQueue, MessageQueue>("ReceiveQueue",
    new InjectionConstructor("receivePath"));

container.RegisterType<IQueue, MessageQueue>("SendQueue",
    new InjectionConstructor("sendPath"));

container.RegisterType<Example>(new InjectionFactory(c =>
    new Example(c.Resolve<IQueue>("ReceiveQueue"),
                c.Resolve<IQueue>("SendQueue"))));

Example example = container.Resolve<Example>();

If you are using a composition root then the use of the magic strings ("ReceiveQueue" and "SendQueue") would be limited to the one registration location.

like image 149
Randy supports Monica Avatar answered Nov 18 '22 10:11

Randy supports Monica


Not everything has to be automatically wired by the container. You can register the Example class like this:

container.Register<Example>(new InjectionFactory(c =>
{
    var receive = new MessageQueue("receivePath");
    var send = new MessageQueue("sendPath");
    return new Example(receive, send);
});
like image 44
Steven Avatar answered Nov 18 '22 09:11

Steven


You could register the two instances with names:

myContainer.RegisterInstance<IQueue>("ReceiveQueue", myReceiveMessageQueue);
myContainer.RegisterInstance<IQueue>("SendQueue", mySendMessageQueue);

and then you should be able to resolve by name, but it requires using the Dependency attribute:

class Example
{
    Example([Dependency("ReceiveQueue")] IQueue receiveQueue, 
            [Dependency("SendQueue")] IQueue sendQueue) {
   }
}

or inject the unity container and then resolve the instances within the constructor:

class Example
{
    Example(IUnityContainter container) 
    {
        _receiveQueue = container.Resolve<IQueue>("ReceiveQueue");
        _sendQueue = container.Resolve<IQueue>("SendQueue");
    }
}

like image 42
Eren Ersönmez Avatar answered Nov 18 '22 10:11

Eren Ersönmez


Well, don't

You should use the factory pattern in this case.

class Example
{
   Example(IQueueFactory factory) 
   {
       _sendQueue = factory.Create("MySend");
       _receiveQueue = factory.Create("MyReceive");
   }
}

It makes the intention a lot more clear and you can internally in the Example class handle if the queues are not found or incorrectly configured.

like image 2
jgauffin Avatar answered Nov 18 '22 09:11

jgauffin


5 years later, but I was looking for this answer too. I worked through it with my own code and then decided to create working code using (slightly altered) classes the OP provided.

This is an entire working example that you can copy into LINQPad (programmer's playground) and run.

Using Statement / Unity Libary

You'll need to add a reference to Microsoft.Practices.Unity.dll You'll also need to add a using statement of :

Microsoft.Practices.Unity

In LinqPad you press F4 to add the reference and the using statement (namespace import).

void Main()
{
    // Create your unity container (one-time creation)
    UnityContainer uc = new UnityContainer();

    // Create simple list to hold your target objects
    // (makes the sample easy to follow)
    List<MessageQueue> allMQs = new List<MessageQueue>();

    // I'm adding TransientLifetimeManager() in order to 
    // explicitly ask for new object creation each time
    // uc.Resolve<MessageQueue>() is called
    uc.RegisterType<IQueue, MessageQueue>(new TransientLifetimeManager());
// ### override the parameters by matching the parameter name (inPath)
    var item = uc.Resolve<MessageQueue>(new ParameterOverride("inPath", "extra.txt").OnType<MessageQueue>());
    allMQs.Add(item);
    item = uc.Resolve<MessageQueue>(new ParameterOverride("inPath", "super.txt").OnType<MessageQueue>());
    allMQs.Add(item);

    foreach (MessageQueue mq in allMQs){
        Console.WriteLine($"mq.Path : {mq.Path}");
    }

    Console.WriteLine("######################\n");

    uc.RegisterType<Example>(new InjectionConstructor((allMQs[0] as IQueue),(allMQs[1] as IQueue)));

    // #### Create a new Example from the UnityContainer
    var example1 = uc.Resolve<Example>();
    // ##### Notice that the Example object uses the default values of super.txt & extra.txt

    Console.WriteLine("#### example1 obj. uses default values ###########");
    Console.WriteLine($"example1.receiver.Path : {example1.receiver.Path}");
    Console.WriteLine($"example1.sender.Path : {example1.sender.Path}");

    // ##################################################
    // Override the parameters that he Example class uses.
 // ### override the parameters by matching the parameter 
 // names (receiveQueue, sendQueue) found in the target
// class constructor (Example class)
    var example2 = uc.Resolve<Example>( 
        new ParameterOverrides {
             {"receiveQueue", new MessageQueue("newReceiveFile")},
             { "sendQueue", new MessageQueue("newSendFile")}
        }.OnType<Example>());

    Console.WriteLine("######################\n");

    Console.WriteLine("#### example1 obj. uses ParameterOverride values ###########");
    Console.WriteLine($"example2.sender.Path : {example2.sender.Path}");
    Console.WriteLine($"example2.receiver.Path : {example2.receiver.Path}");
}

class Example
{
   public MessageQueue receiver {get;set;}
   public MessageQueue sender {get;set;}

   public Example(IQueue receiveQueue, IQueue sendQueue) {
    this.receiver = receiveQueue as MessageQueue;
    this.sender = sendQueue as MessageQueue;

   }
}

public class MessageQueue : IQueue
{
    public string Path {get;set;}
    public MessageQueue(string inPath) {
        Path = inPath;}
}

interface IQueue{

}

Output For Examination

If you run the script above you'll see sample output which will look like the following:

mq.Path : extra.txt
mq.Path : super.txt
######################

#### example1 obj. uses default values ###########
example1.receiver.Path : extra.txt
example1.sender.Path : super.txt
######################

#### example1 obj. uses ParameterOverride values ###########
example2.sender.Path : newSendFile
example2.receiver.Path : newReceiveFile
like image 2
raddevus Avatar answered Nov 18 '22 09:11

raddevus