Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac sub-dependencies chain registration

Question

How do I construct an AutoFac ContainerBuilder such that my sub-dependencies are correctly resolved (assuming more than one concrete implementation of an interface)? Default registration/resolve will not work because I have more than one concrete implementation of a sub-dependency, and the sub-dependency resolution is dependent on the resolution of the primary object. In all cases I wish to use constructor injection.

Scenario

For example, lets say I need to print a receipt, so I create an interface called IReceipt, with one method, called PrintReceipt().

But I need to print 3 kinds of receipts

  1. Standard Receipt
  2. Gift Receipt
  3. Email Receipt

All receipt types have different formats, and the email receipt is not printed at all, rather it is emailed. So I wish my IReceipt had the ability to depend on a formatter, and a processor. Say I invent IProcessor, with the method Process(), and it can either be a printer processor, or an email processor (the printer processor has the responsibility of printer communications, and the email processor has the responsibility of communicating with the SMTP server). Also, I create an IFormatter, and it can feed input to the processor object, formatted as a standard receipt, or a gift receipt, or even HTML for the email receipt. So the constructor on any concrete implementation of an IReciept now requires two dependencies - an IFormatter, and an IProcessor.

Now, at the root composition I need to decide, am I resolving an IReceipt that is intended for a standard receipt, a Gift receipt, or an email receipt. I would like to call the container Resolve() method passing the necessary parameters so it will resolve the correct IReceipt. Furthermore, I would like the registration ContainerBuilder to know that if I attempt to resolve the IReceipt Standard Receipt concrete implementation that it needs to resolve sub-depencies with the correct Standard Receipt IFormatter, and correct Standard Receipt IProcessor. The same would hold true for the gift receipt scenario and the email receipt scenario.

Recap

So - in all this - my question is - how do I construct the ContainerBuilder so sub-dependencies are defined at design time, and that a single call to Resolve() will correctly identify the required concrete implementations? I am not seeking a solution to talk to a printer, or post HTML. This question is strictly for the Autofac registration and resolution methodology. At a different client, I have used this exact tactic using CastleWindsor but my current client is using Autofac.

like image 722
barrypicker Avatar asked Sep 30 '14 17:09

barrypicker


1 Answers

OK, so I figured out a way to perform the sub-dependency chaining. Again, the desire is to call resolve once per root composition object and have all sub-dependencies satisfied the entire chain down. Not looking to get into a religious debate about best practices - whether or not factory methods, service locators, etc. should be implemented. I intentionally left out IoC scoping as it is not the subject of my original question.

This contrived example does not implement email or printer functionality, but it is stubbed out now. The whole point is to show how to pre-define the entire dependency chain. Now automated unit tests will be easier to implement.

Code

Main Program

using System;
using System.Collections.Generic;
using Autofac;

namespace MyAutoFacTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // CREATE THE IOC ENGINE AND CONTAINER
            var builder = new Autofac.ContainerBuilder();
            Autofac.IContainer container;

            // CREATE THE DEPENDENCY CHAIN REGISTRATION
            builder.RegisterType<Printer>()
                .Named<IPrinter>("My Default Printer");

            builder.RegisterType<PrinterProcessor>()
                .Named<IProcessor>("PrinterProcessor")
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IPrinter>("My Default Printer"));

            builder.RegisterType<EmailProcessor>()
                .Named<IProcessor>("EmailProcessor");

            builder.RegisterType<StandardReceiptFormatter>()
                .Named<IFormatter>("StandardReceiptFormatter");

            builder.RegisterType<GiftReceiptFormatter>()
                .Named<IFormatter>("GiftReceiptFormatter");

            builder.RegisterType<EmailReceiptFormatter>()
                .Named<IFormatter>("EmailReceiptFormatter");

            builder.RegisterType<Receipt>()
                .Named<IReceipt>("StandardReceipt")
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("PrinterProcessor"))
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("StandardReceiptFormatter"));

            builder.RegisterType<Receipt>()
                .Named<IReceipt>("GiftReceipt")
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("PrinterProcessor"))
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("GiftReceiptFormatter"));

            builder.RegisterType<Receipt>()
                .Named<IReceipt>("EmailReceipt")
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IProcessor>("EmailProcessor"))
                .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IFormatter>("EmailReceiptFormatter"));

            // COMPILE THE AUTOFAC REGISTRATION
            container = builder.Build();

            // SETUP INITIALIZATION STUFF - THINGS THAT WOULD ORDINARILY HAPPEN AS PART OF EXTERNAL SYSTEMS INTEGRATION
            int someBogusDatabaseIdentifier = 1;

            var standardReceipt = container.ResolveNamed<IReceipt>("StandardReceipt");
            standardReceipt.PrintReceipt(someBogusDatabaseIdentifier);

            var giftReceipt = container.ResolveNamed<IReceipt>("GiftReceipt");
            giftReceipt.PrintReceipt(someBogusDatabaseIdentifier);

            var emailReceipt = container.ResolveNamed<IReceipt>("EmailReceipt");
            emailReceipt.PrintReceipt(someBogusDatabaseIdentifier);

            Console.ReadLine();
        }
    }
}

Interfaces

IPrinter

namespace MyAutoFacTest
{
    public interface IPrinter
    {
        void Print();
    }
}

IReceipt

namespace MyAutoFacTest
{
    public interface IReceipt
    {
        void PrintReceipt(int id);
    }
}

IProcessor

namespace MyAutoFacTest
{
    public interface IProcessor
    {
        void Process(string formattedString);
    }
}

IFormatter

namespace MyAutoFacTest
{
    public interface IFormatter
    {
        string GetFormattedString(int id);
    }
}

Concrete Implementations

Will show the leaf dependencies first...

Printer

using System;

namespace MyAutoFacTest
{
    public class Printer : IPrinter
    {
        public void Print()
        {
            Console.WriteLine("Printer is printing");
        }
    }
}

Printer Processor

using System;

namespace MyAutoFacTest
{
    public class PrinterProcessor : IProcessor
    {
        private IPrinter _printer;

        public PrinterProcessor(IPrinter printer)
        {
            this._printer = printer;
        }

        public void Process(string formattedString)
        {
            Console.WriteLine("Printer processor sending receipt to printer.");
            this._printer.Print();
        }
    }
}

Email Processor

using System;

namespace MyAutoFacTest
{
    public class EmailProcessor : IProcessor
    {
        public void Process(string formattedString)
        {
            Console.WriteLine("Email Processor sending out an email receipt");
        }
    }
}

Standard Receipt Formatter

namespace MyAutoFacTest
{
    public class StandardReceiptFormatter : IFormatter
    {
        public string GetFormattedString(int id)
        {
            return "StandardReceiptFormatter formatted string";
        }
    }
}

Gift Receipt Formatter

namespace MyAutoFacTest
{
    public class GiftReceiptFormatter : IFormatter
    {
        public string GetFormattedString(int id)
        {
            return "GiftReceiptFormatter formatted string";
        }
    }
}

Email Receipt Formatter

namespace MyAutoFacTest
{
    public class EmailReceiptFormatter : IFormatter
    {
        public string GetFormattedString(int id)
        {
            return "EmailReceiptFormatter formatted string";
        }
    }
}

Receipt

using System;

namespace MyAutoFacTest
{
    public class Receipt : IReceipt
    {
        private IFormatter _formatter;
        private IProcessor _processor;

        public Receipt(IFormatter formatter, IProcessor processor)
        {
            this._formatter = formatter;
            this._processor = processor;
        }

        public Receipt(IFormatter formatter)
        {
            this._formatter = formatter;
        }

        public Receipt(IProcessor processor)
        {
            this._processor = processor;
        }

        public void PrintReceipt(int id)
        {
            var formattedString = this._formatter.GetFormattedString(id);
            Console.WriteLine(formattedString);

            this._processor.Process(formattedString);
        }
    }
}

Conclusion

Most of the noise associated with objects that need to be created is bundled into one location. In practice I would likely move the registration to its own chunk of code (Perhaps a static class). Keeping the noise out of the functional classes yields nice clean code with a focus on functional intent. All the wire-up is early in the process, and now out of the way.

Looking at the registration in particular, my contrived example has 4 layers of dependencies..

  1. Main
  2. Receipt object
  3. Processors and Formatters
  4. Printer (Only used on Printer Processor)(Could probably express an email server dependency, but I think this example is fairly clear as-is).

By using the Autofac.Core.ResolvedParameter we can reference other registered objects (by name). This will keep the registration long, but flat. Any hierarchy is expressed (shallowly - if that's a word) as parent-child only, and very re-usable. Resolve is only called on the root composition objects - the 3 receipt engines (Standard, Gift, and Email). For each root composition object the entire chain of dependencies is now resolvable.

like image 86
barrypicker Avatar answered Sep 18 '22 05:09

barrypicker