Injecting array / enumerator

Jul 9, 2014 at 1:53 PM
Edited Jul 9, 2014 at 1:54 PM
Hi,

I need to notify a certain set of events from my business manager class by email & SMS (tomorrow there could be another mechanism - lets say WhatsApp). Assuming all of them implement IEventListener interface as below:
public interface IEventListener
{
   void OnSomeEvent(SomeEventArgs e);
}

public class EmailNotifier : IEventListener
{
   public void OnSomeEvent(SomeEventArgs e)
   {
      // code to send email
   }
}

public class SMSNotifier : IEventListener
{
   public void OnSomeEvent(SomeEventArgs e)
   {
      // code to send SMS
   }
} 
I am trying to figure out how to register both for injection, and how to inject both of them in the manager so that it can notify the event to both the listeners.

I looked at Decorated collections but I did not understand how to use if (and if that is the way to go in my scenario).

Looking for some ideas / gyan.

Thanks & regards,
Vikram
Coordinator
Jul 9, 2014 at 2:19 PM
I think there's more interesting information in the Batch Registration section, but registering the event listeners can be done as follows:
container.RegisterAll<IEventListener>(typeof(EmailNotifier), typeof(SMSNotifier));
Now if that manager takes a dependency on IEnumerable<IEventListener>, an enumerable with all your event listeners will get injected.

If however there are multiple classes that need to handle those event listeners, it's best to hide them behind a composite:
public class CompositeEventListener : IEventListener {
    private readonly IEnumerable<IEventListener > listeners;

    public CompositeEventListener(IEnumerable<IEventListener > listeners) {
        this.listeners = listeners;
    }

    public void OnSomeEvent(SomeEventArgs e)) {
        foreach (var listener in this.listeners) {
            listener.OnSomeEvent(e);
        }
    }
}
With this CompositeEventListener you can do the registration as follows:
container.RegisterAll<IEventListener>(typeof(EmailNotifier), typeof(SMSNotifier));
container.RegisterSingle<IEventListener, CompositeEventListener>();
Now any class that takes an IEventListener in its constructor, will get the CompositeEventListener injected, and the CompositeEventListener depends on the list over event listeners.
Marked as answer by dot_NET_Junkie on 8/11/2014 at 12:36 PM
Jul 10, 2014 at 5:45 AM
Thanks DNJ. This should suit us just fine.

Also, I like the idea of using a CompositeEventListener. That will free up the business managers from looping through individual listeners.

Regards,
Vikram
Jul 10, 2014 at 6:10 AM
I presume there is no real need for CompositeEventListener to be a singleton.

Vikram
Coordinator
Jul 10, 2014 at 8:40 AM
Edited Jul 10, 2014 at 8:41 AM
There's no strict need for it to be singleton, but since it doesn't have any state and only depends on other singletons (IEnumerable<T>'s are singleton in Simple Injector) this is good for performance (not that performance is really an issue in Simple Injector) and it makes it possible to inject it into components that are not registered as transient.
Jul 10, 2014 at 12:26 PM
Edited Jul 10, 2014 at 12:27 PM
This is getting a bit involved. I will actually code and try this out, but will put it down here - in case you may point out potential problem in using IEnumerable<T>.

As you have indicated IEnumerable<T> is singleton. That in itself is not a problem, but what about the instances of IEventListener (EmailNotifier and SMSNotifier) provided by the enumerator? Will they be singleton? or their life cycle is managed separately (e.g WebApiRequestLifestyle)?

We don't intend to actually dispatch the email / SMS right when the event occurs. The EmailNotifier actually persists the email details in an Email Q store. It is later picked by a watching process and dispatched. This helps us simplify handling the retries etc. in case of error in sending email. Also, to schedule the actual dispatches on low server load times etc.

So, in the sample code of CompositeEventListener that you have indicate
public void OnSomeEvent(SomeEventArgs e)) {
   foreach (var listener in this.listeners) {
      listener.OnSomeEvent(e);
   }
}
will it be a new instance of listener for each web request?

I am sorry I am asking so many questions. I hope its not a bother.

Thanks & Regards,
Vikram
Coordinator
Jul 10, 2014 at 12:57 PM
Edited Jul 10, 2014 at 12:58 PM
Hi @Viklele,

Please see this excerpt taken from some of the new documentation
Note: Simple Injector preserves the lifestyle of instances that are returned from an injected IEnumerable<T> instance. In reality you should not see the the injected IEnumerable<IValidator<T>> as a collection of implementations, you should consider it a stream of instances. Simple Injector will always inject a reference to the same stream (the IEnumerable<T> itself is a Singleton) and each time you iterate the IEnumerable<T>, for each individual component, the container is asked to resolve the instance based on the lifestyle of that component. Regardless of the fact that the CompositeValidator<T> is registered as singleton the validators it wraps will each have their own specific lifestyle.
Each instance returned from the IEnumerable will come with the particular lifestyle it has been registered as requiring.

P.S. don't worry about asking questions, keep asking :-)
Coordinator
Jul 10, 2014 at 1:00 PM
> but what about the instances of IEventListener (EmailNotifier and SMSNotifier) provided by the enumerator? Will they be singleton? or their life cycle is managed separately (e.g WebApiRequestLifestyle)?

Simple Injector works a bit differently in some cases than the other DI libraries do. Most libraries will inject an array or list into your class any time you expose an IEnumerable<T>. That means that every inject a new array has to be created in memory.

Simple Injector takes a different approach. The injected 'collection' is really an enumerator. It produces new instances every time you iterate it. So you can see it as a factory and it will spit out instances based on their lifestyle. Take the following example for instance:
container.Register<EmailNotifier>(Lifestyle.Singleton);
container.Register<SMSNotifier>(Lifestyle.Transient);
container.RegisterAll<IEventListener>(typeof(EmailNotifier), typeof(SMSNotifier));

var mailNotifier1 = container.GetAllInstances<IEventListener>().First();
var mailNotifier2 = container.GetAllInstances<IEventListener>().First();

var smsNotifier1 = container.GetAllInstances<IEventListener>().Last();
var smsNotifier2 = container.GetAllInstances<IEventListener>().Last();

Assert.AreSame(mailNotifier1, mailNotifier2);
Assert.AreNotSame(smsNotifier1, smsNotifier2);
In the previous example EmailNotifier is registered as Singleton, while SMSNotifier is registered as transient. Simple Injector will obey the lifestyle registrations of the registered instances. This means that no matter how many time the first element (the EmailNotifier) is requested from the collection, you are guaranteed to get the same instance. Since the SMSNotifier however is registered as transient, every time you iterate the enumerable, you will get a new instance.

Not only does this work with Singleton and Transient, this works for every lifestyle you specify.
Jul 10, 2014 at 1:43 PM
Thanks qujck, DNJ.

This is just perfect. Can't thank you enough. Beautiful stuff.

Also, thanks for the encouragement for asking questions :-)
  • Vikram