Search code examples
c#dependency-injectionsimple-injector

Resolving components when using container.RegisterAll<TService>()


I'm having a problem when trying to inject dependencies in my classes. I'm just trying it around to learn more about simple injection and DI when I got stuck here.

So this is my Main method:

static void Main(string[] args)
{
    var container = new Container();

    // Registrations here
    container.RegisterAll<ISimpleLogger>(typeof(DebugLogger), typeof(ConsoleLogger));

    container.Verify();

    Product p = new Product();
    p.TestLog();

    Console.ReadKey();
}

and here are my other classes:

public interface ISimpleLogger
{
    void Log(string content);
}

public class DebugLogger : ISimpleLogger
{
    public void Log(string content)
    {
        Debug.WriteLine(content);
    }
}

public class ConsoleLogger : ISimpleLogger
{
    public void Log(string content)
    {
        Console.WriteLine(content);
    }
}

public class Product
{
    public string Name { get; private set; }
    public decimal Price { get; private set; }
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    private readonly ISimpleLogger[] loggers;

    public Product(params ISimpleLogger[] _loggers)
    {
        this.loggers = _loggers;
    }

    public void TestLog()
    {
        foreach (var item in loggers)
        {
            item.Log("testing...");
        }
    }

    public static List<Product> GetSampleProducts()
    {
        return new List<Product>
        {
            new Product { Name="West Side Story", Price = 9.99m },
            new Product { Name="Assassins", Price=14.99m },
            new Product { Name="Frogs", Price=13.99m },
            new Product { Name="Sweeney Todd", Price=10.99m}
        };
    }
}

1) I cannot see the output "Testing..." anywhere.

2) Debugging my app, when the Product constructor is called, it doesn't inject the dependencies, so my array stays with 0 items.

What it should be? A misconfiguration when setting up the container?


Solution

  • With Dependency Injection we build up object graphs of components. Components are the classes in our application that contain the application’s behavior. Such object graph typically consists of long-lived services. Those services themselves should contain no state. State (or runtime data) should instead be pushed through this object graph using method calls. Any time you violate this basic principle, you are heading for trouble.

    Your Product class is an entity. This is a short-lived object that contains state. Prevent your DI library from building up objects that contain state. This leads to ambiguity and maintainability issues.

    This is already happening in your case, because you would like to create a Product class, which you like to be immutable. So this means it needs to be initialized through the constructor. On the other hand, you also want its dependencies to be injected into the constructor, for which you have a second constructor. But you can only call one constructor; so your Product either is a valid product without dependencies, or an invalid product with dependencies.

    Although you could solve this by merging the two constructors together, this would lead to the same amount of trouble, because now you will have to ask the DI container to build up this Product for you, but the DI container knows nothing about the primitives it has to supply (in your case the name and the price).

    In my projects, I usually use an anemic domain model. This means that my entities don't have any logic in them. Instead I use commands and queries as a definition of the verbs of my domain model. Others don't like this and like to have domain logic as part of domain objects (although these two models aren't mutually exclusive, some developers use these concepts in combination). Since domain logic sometimes needs to work with services, those entities obviously need these dependencies to be supplied. This doesn't mean however that you are forced to use constructor injection here.

    Method injection is much more convenient in this case. This means that your Product class will look like this:

    public class Product
    {
        public string Name { get; private set; }
        public decimal Price { get; private set; }
        public Product(string name, decimal price) {
            Name = name;
            Price = price;
        }
    
        public void TestLog(ISimpleLogger[] loggers) {
            foreach (var item in loggers)
            {
                item.Log("testing...");
            }
        }
    }
    

    So instead of passing the dependencies through the constructor, we pass it in through the method that actually needs it. Method injection works much better because:

    • Your constructor conflict is gone. You can now create a valid instance using the constructor, and supply the required services when needed.
    • It will prevent your constructors to get big with many dependencies. This will happen, because while most methods on your entity just need one or two different services, all the methods on your entity, might need a dozen or more dependencies together. This gets really ugly really fast. You will often see that there isn’t that much cohesion between the methods on your domain objects, and this is why I move this logic into my command handlers (but that’s a different story).

    Of course, the responsibility of injecting these dependencies is now moved to the one who calls this domain method (the TestLog method in your case). But this is typically not a problem, because this consumer will be a normal application component where you can apply constructor injection again. Here’s an example:

    public class LogProductCommandHandler : ICommandHandler<LogProduct>
    {
        private readonly IRepository<Product> productRespository;
        private readonly ISimpleLogger[] loggers;
        public LogProductCommandHandler(IRepository<Product> productRespository,
            ISimpleLogger[] loggers) {
            this.productRespository = productRespository;
            this.loggers = loggers;
        }
    
        public void Handle(LogProduct command) {
            Product product = this.productRepository.GetById(command.ProductId);
            product.TestLog(this.loggers);
        }
    }
    

    This LogProductCommandHandler can be resolved by the container. Do note that all runtime data 'flows' through the object graph here. We have this LogProduct message (the command) which contains runtime data. It is passed on to the Handle method. This message contains a ProductId value and it is passed on to the repository's GetById method that will return the Product (again runtime data).

    One extra thing though. Prevent injecting lists of things into consumers. Injecting an T[] array, IEnumerable<T> or List<T> often means you are leaking implementation details. The consumer shouldn't have to know or be concerned with the fact that there might be multiple implementations of -in your case- a logger. Injecting a collection complicates the consumer, because it has to iterate the collection. Not only that, but each consumer that depends that collection needs to iterate this collection. This not only needlessly complicates the code of all those consumers, it will make your code harder to change. At one point in time there will come a moment that you want to change the way that those loggers are processed. Perhaps you want to continue logging to the next logger, even if the first logger threw an exception. I'm sure you know how to program such thing; it isn't a really difficult problem to solve. But it will cause you to make sweeping changes through your code base to get this in.

    Instead, make use of the Composite design pattern. With the composite pattern you hide a collection of elements of some abstraction behind a component that implements the same abstraction. A composite implementation of your ISimpleLogger might look like this:

    public sealed SimpleLoggerComposite : ISimpleLogger
    {
        private readonly IEnumerable<ISimpleLogger> loggers;
        public SimpleLoggerComposite(IEnumerable<ISimpleLogger> loggers) {
            this.loggers = loggers;
        }
        public void Log(string message) {
            foreach (var logger in this.loggers) {
                logger.Log(message);
            } 
        }
    }
    

    Here we moved the foreach loop to the SimpleLoggerComposite. Now we can register the SimpleLoggerComposite as follows:

    // Simple Injector v3.x
    container.RegisterSingleton<ISimpleLogger, SimpleLoggerComposite>();
    container.RegisterCollection<ISimpleLogger>(
        new[] { typeof(DebugLogger), typeof(ConsoleLogger) });
    
    // Simple Injector v2.x
    container.RegisterSingle<ISimpleLogger, SimpleLoggerComposite>();
    container.RegisterAll<ISimpleLogger>(
        new[] { typeof(DebugLogger), typeof(ConsoleLogger) });
    

    Now the rest of the application can simply depend on ISimpleLogger instead of ISimpleLogger[]. For instance:

    public class Product
    {
        ...
    
        public void TestLog(ISimpleLogger logger) {
            logger.Log("testing...");
        }
    }
    

    Now the code within your TestLog method is becoming really boring. But that's obviously a good thing. The trick with software development is to make software that looks like it is really simple :-)