3

I want to write a factory for creating various types of "xxNotification" classes. My concrete "xxNotification" has dependencies registered with AutoFac. I would like to get/resolve instance of "xxNotification" using Autofac. How to do that?

public interface INotification
{
    void Notify(string Action, int OrderID);
}

public class MagentoOrderStateNotification : INotification
{
    private readonly GenericRepository<Order> _orderRepository;
    private readonly OMRestIntegrationService _oMRestIntegrationService;

    public MagentoOrderStateNotification(GenericRepository<Order> orderRepository, OMRestIntegrationService oMRestIntegrationService)
    {
        _orderRepository = orderRepository;
        _oMRestIntegrationService = oMRestIntegrationService;
    }

    public void Notify(string Action, int OrderID)
    {
        //implementation...
    }
}

public class NotificationFactory
{
    public INotification GetNotification(string NotificationName)
    {
        switch (NotificationName)
        {
            case "MagentoOrderStateNotification":
                //resolve instance of MagentoOrderStateNotification
        }
    }
}
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
vpetrovic
  • 527
  • 4
  • 22
  • 1
    Generally, you don't "get" an object from a DI container. That is anti-pattern known as a service locator. Instead, the application is composed at startup including the factory and its dependencies. It's called the [Hollywood principle: Don't call us, we'll call you](https://en.wikipedia.org/wiki/Inversion_of_control). – NightOwl888 Oct 05 '17 at 15:39
  • OK, so what if I need to instantiate a service at runtime based on some configuration like "for this order state, call that and that INotification"? – vpetrovic Oct 05 '17 at 20:55
  • You can pass the runtime order state into your service via a method parameter. Within your service you can use a [creational object patten](https://sourcemaking.com/design_patterns/creational_patterns), a [strategy pattern](https://stackoverflow.com/a/34331154/) or perhaps [just use generics](https://stackoverflow.com/a/41811716) to select the service or services to use. You could also [combine patterns](https://stackoverflow.com/a/31971691/) depending on the scenario. – NightOwl888 Oct 06 '17 at 00:56

1 Answers1

3

This looks like a good candidate for the Strategy Pattern.

Interfaces

public interface INotification
{
    void Notify(string Action, int OrderID);
    bool AppliesTo(string NotificationName);
}

public interface INotificationStrategy
{
    void Notify(string NotificationName, string Action, int OrderID);
}

INotification Implementations

public class MagentoOrderStateNotification : INotification
{
    private readonly GenericRepository<Order> _orderRepository;
    private readonly OMRestIntegrationService _oMRestIntegrationService;

    public MagentoOrderStateNotification(GenericRepository<Order> orderRepository, OMRestIntegrationService oMRestIntegrationService)
    {
        _orderRepository = orderRepository;
        _oMRestIntegrationService = oMRestIntegrationService;
    }

    public void Notify(string Action, int OrderID)
    {
        //implementation...
    }

    public bool AppliesTo(string NotificationName)
    {
        // Note that you can make the criteria to use this
        // service as advanced as you need to. You could even
        // design your strategy to use multiple services in specific
        // scenarios. But putting that logic here has many advantages
        // over a switch case statement.
        return NotificationName == "a";
    }
}

public class FooOrderStateNotification : INotification
{
    public void Notify(string Action, int OrderID)
    {
        //implementation...
    }

    public bool AppliesTo(string NotificationName)
    {
        return NotificationName == "b";
    }
}

Strategy

public class NotificationStrategy : INotificationStrategy
{
    private readonly IEnumerable<INotification> oNotifications;

    public MessageStrategy(IEnumerable<INotification> oNotifications)
    {
        if (oNotifications == null)
            throw new ArgumentNullException("oNotifications");
        this.oNotifications = oNotifications;
    }

    public void Notify(string NotificationName, string Action, int OrderID)
    {
        var notification = this.oNotifications
            .FirstOrDefault(x => x.AppliesTo(NotificationName));

        // Possible alternative: get multiple implementations and
        // then loop through them, executing each one.
        // var notifications = this.oNotifications
        //     .Where(x => x.AppliesTo(NotificationName)).ToArray();

        if (notification == null)
        {
            throw new Exception("No notification type registered for " + NotificationName);
        }

        notification.Notify(Action, OrderID);
    }
}

Usage

public class SomeService : ISomeService
{
    private readonly INotificationStrategy oNotificationStrategy;

    public SomeService(INotificationStrategy oNotificationStrategy)
    {
        if (oNotificationStrategy == null)
            throw new ArgumentNullException("oNotificationStrategy");
        this.oNotificationStrategy = oNotificationStrategy;
    }

    public void DoSomething()
    {
        this.oNotificationStrategy.Notify("a", "Hello", 1234);
    }
}

Note that an alternative approach could be to use some "OrderType" state based on the OrderID to lookup the service to use (not sure it actually makes sense to pass OrderID into the Notify() method). But however you slice it this is an application design problem, not a DI problem.

Autofac Registration

There are a few ways you can configure Autofac to register multiple implementations of the same interface for use in the NotificationStrategy constructor. Here are a couple of examples:

Scan

// Note you may need more than one registration if you are spanning
// multiple assemblies.
builder.RegisterAssemblyTypes(typeof(INotification).Assembly)
   .Where(x => typeof(INotification).IsAssignableFrom(x))
   .AsImplementedInterfaces();

When the implementations are registered this way, Autofac will implicitly inject all implementations into any IEnumerable<INotification> constructor parameter.

Cherry Pick

builder.Register<MagentoOrderStateNotification>()
    .Named<INotification>("magentoOrderStateNotification");
builder.Register<FooOrderStateNotification>()
    .Named<INotification>("fooOrderStateNotification");

builder.RegisterType<NotificationStrategy>()
            .As<INotificationStrategy>()
            .WithParameter(
                (p, c) => p.Name == "oNotifications",
                (p, c) => new[]
                    {
                        c.ResolveNamed<INotification>("magentoOrderStateNotification"),
                        c.ResolveNamed<INotification>("fooOrderStateNotification")
                    });
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • How will NotificationStrategy get INotification[] oNotifications as constructor parameter? – vpetrovic Oct 06 '17 at 07:34
  • I updated the answer. Do note that for the scan to work in Autofac, the `NotificationStrategy` constructor needs to be changed to `IEnumerable` instead of `INotification[]`. – NightOwl888 Oct 06 '17 at 07:58
  • What if I only want one of the strategy implementations to be registered based on some runtime condition, such as a subcommand specified to a CLI framework like CliFx? From the composition root it's not easy to know which concrete implementation should be created – void.pointer Apr 25 '21 at 18:59
  • @void.pointer - That is what the `AppliesTo` method is for. Register all of the strategy instances at startup and then select one or more instances to use at runtime by putting your filter logic in the `AppliesTo` method. Since all instances are always loaded, it is usually best to keep the number of objects capped at a few hundred, after that you might be better off with an abstract factory. – NightOwl888 Apr 26 '21 at 20:52
  • So basically in my case you think it would be non-abusive to do something like `builder.Register(c => c.Resolve>().First(cmd => cmd.IsActive)).As()`? Then in constructors I can do `public MyObject(ICommand cmd)` and get the command chosen by the user at the command line? So far I've been using an `IActiveCommandProvider`, but your idea is much cleaner. – void.pointer May 01 '21 at 17:18
  • Generally speaking, my advice for console applications with DI is to use the container to resolve 1 object that represents the entire application and for that object to have a `Run(object[] args)` parameter. Once that object is resolved, the application is unaware that there is a container at all. – NightOwl888 May 02 '21 at 09:57
  • That being said, I have had the need to build a command line app without reinventing the wheel, and I ended up going with a customized version of [`Microsoft.Extensions.CommandLineUtils`](https://dejanstojanovic.net/aspnet/2018/june/building-advanced-net-core-global-tool/). It doesn't follow good DI practices, but handles all of the details about commands, options, and help. – NightOwl888 May 02 '21 at 10:03