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
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.
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.
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();
}
}
}
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);
}
}
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..
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With