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

6

10

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.

Cosby answered 27/8, 2013 at 5:37 Comment(0)
D
7

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.

Dispensary answered 27/8, 2013 at 17:2 Comment(0)
I
4

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);
});
Iridize answered 27/8, 2013 at 7:59 Comment(4)
The OP has a single class MessageQueue -- no separate ReceiveQueue and SendQueue classes.Gisellegish
@ErenErsönmez: Thanks for bringing this to my attention; I missed this point. This makes my answer much shorter. I updated my answer.Iridize
@Steven: Your original answer was really interesting. If creating new classes and interfaces is an option I think that solution looks good.Cosby
@ErikZ: It's important to try remove ambiguity from the design, but after rereading your question more thoroughly (thanks to Eren) I noticed that adding interfaces might not be the right solution in your case. However, it's always good to keep this in mind.Iridize
G
3

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");
    }
}

Goldstein answered 27/8, 2013 at 5:51 Comment(3)
Although this answers the question, it's not a very good solution, because this clutters the application with attributes and couples the application to the Unity container. I would rather use an InjectionFactory instead.Iridize
I'm sorry, but your second (updated) example is a bad idea, that's why you get my downvote. You should not promote injecting the container into classes, that is bad practice.Iridize
@Iridize you're right, I take back that edit. the linked article has some very valid points.Gisellegish
P
2

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.

Physoclistous answered 27/8, 2013 at 6:19 Comment(7)
Thank you! That was my first thought, but how do I avoid newing up the MessageQueue classes in the factory. Should I inject the container into the factory and use the ParameterOverrides proposed in another answer?Cosby
A factory is especially useful for delaying the creation of instances, but in your example you're still creating those queues during object graph construction. We could even argue that you complicated the design of Example, since it now depend on an extra abstraction. I would remove the IQueueFactory dependency from Example and if the use of the factory is needed, register it using an InjectionFactory as follows: container.Register<Example>(new InjectionFactory(c => new Example(c.Resolve<IQueueFactory>().Create("Receive"), c.Resolve<IQueueFactory().Create("Send"))));.Iridize
@ErikZ: Ok. so you are sharing the queues between different classes?Physoclistous
@jgauffin: No, the queues is only used inside the Example class.Cosby
The the queues will get the same lifetime as your Example class if you use a factory. I therefore think that it's the most straightforward solution as the actual creation only becomes an implementation detail.Physoclistous
@jgauffin: That makes sense, but should I inject the container into the factory?Cosby
@Steven: It's only a matter of where the factory is used. The question is what makes the code easiest to read.Cosby
R
2

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
Reprobate answered 20/5, 2019 at 17:33 Comment(0)
H
1

I think this has been asked before on Stackoverflow. You need to use ParameterOverride:

ParameterOverride enables you to pass in values for constructor parameters to override a parameter passed to a given named constructor. Only the parameter value is overridden, not the constructor.

Link to MSDN Article

Link to Stackoverflow Article

var exampleInstance = new Example();

var queue1 = unityContainer.Resolve<IQueue>(new ParameterOverrides<MessageQueue> { { "path", "yourPath" }});

var queue2 = unityContainer.Resolve<IQueue>(new ParameterOverrides<MessageQueue> { { "path", "yourPath2Queue2" }});

exampleInstance.Example(queue1,queue2);
Hourigan answered 27/8, 2013 at 5:53 Comment(1)
Thank you! But I want to resolve Example like this, container.Resolve<Example>(). How do I specify the two different MessageQueue instances?Cosby

© 2022 - 2024 — McMap. All rights reserved.