Autofac子依赖链注册

如何构造AutoFac ContainerBuilder,以便正确解析我的子依赖关系(假设接口的多个具体实现)? 默认注册/解析将不起作用,因为我有多个子依赖项的具体实现,并且子依赖项解析依赖于主对象的分辨率。 在所有情况下,我希望使用构造函数注入。

脚本

例如,假设我需要打印收据,因此我创建了一个名为IReceipt的接口,其中一个方法称为PrintReceipt()。

但我需要打印3种收据

  1. 标准收据
  2. 礼品收据
  3. 电子邮件收据

所有收据类型都有不同的格式,电子邮件收据根本不打印,而是通过电子邮件发送。 所以我希望我的IReceipt能够依赖格式化器和处理器。 假设我使用Process()方法创建IProcessor,它可以是打印机处理器,也可以是电子邮件处理器(打印机处理器负责打印机通信,电子邮件处理器负责与SMTP服务器通信) 。 此外,我创建了一个IFormatter,它可以将输入提供给处理器对象,格式化为标准收据,或礼品收据,甚至是电子邮件收据的HTML。 因此,关于IReciept的任何具体实现的构造函数现在需要两个依赖项–IFormatter和IProcessor。

现在,根据我需要决定的根构成,我是否正在解决用于标准收据,礼品收据或电子邮件收据的IReceipt。 我想调用容器Resolve()方法传递必要的参数,以便它解析正确的IReceipt。 此外,我希望注册ContainerBuilder知道,如果我尝试解决IReceipt标准收据的具体实现,它需要使用正确的标准收据IFormatter解决子依赖关系,并更正标准收据IProcessor。 对于礼品收据方案和电子邮件收据方案也是如此。

概括

所以 – 在所有这些 – 我的问题是 – 我如何构造ContainerBuilder,以便在设计时定义子依赖,并且单次调用Resolve()将正确识别所需的具体实现? 我不是在寻求与打印机对话或发布HTML的解决方案。 此问题严格适用于Autofac注册和解决方法。 在不同的客户端,我使用CastleWindsor使用了这个确切的策略,但我当前的客户端使用的是Autofac。

好的,所以我想出了一种执行子依赖链接的方法。 同样,希望每个根组合对象调用一次解析,并使所有子依赖关系满足整个链。 不打算就最佳实践进行宗教辩论 – 无论是否应实施工厂方法,服务定位器等。 我故意遗漏了IoC范围,因为它不是我原来问题的主题。

这个人为的例子没有实现电子邮件或打印机function,但它现在已被删除。 重点是展示如何预定义整个依赖关系链。 现在,自动化unit testing将更容易实现。

主体计划

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() .Named("My Default Printer"); builder.RegisterType() .Named("PrinterProcessor") .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("My Default Printer")); builder.RegisterType() .Named("EmailProcessor"); builder.RegisterType() .Named("StandardReceiptFormatter"); builder.RegisterType() .Named("GiftReceiptFormatter"); builder.RegisterType() .Named("EmailReceiptFormatter"); builder.RegisterType() .Named("StandardReceipt") .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("PrinterProcessor")) .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("StandardReceiptFormatter")); builder.RegisterType() .Named("GiftReceipt") .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("PrinterProcessor")) .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("GiftReceiptFormatter")); builder.RegisterType() .Named("EmailReceipt") .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("EmailProcessor")) .WithParameter(Autofac.Core.ResolvedParameter.ForNamed("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("StandardReceipt"); standardReceipt.PrintReceipt(someBogusDatabaseIdentifier); var giftReceipt = container.ResolveNamed("GiftReceipt"); giftReceipt.PrintReceipt(someBogusDatabaseIdentifier); var emailReceipt = container.ResolveNamed("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); } } 

具体实施

将首先显示叶依赖…

打印机

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

打印机处理器

 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(); } } } 

邮件处理器

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

标准收据格式化程序

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

礼品收据格式化程序

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

电子邮件收据格式化程序

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

收据

 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); } } } 

结论

与需要创建的对象相关联的大多数噪声被捆绑到一个位置。 在实践中,我可能会将注册移动到自己的代码块(也许是静态类)。 将噪声排除在函数类之外会产生漂亮的干净代码,并且关注function意图。 所有的电汇都是在这个过程的早期,现在已经不在了。

特别是看注册,我设计的例子有4层依赖。

  1. 主要
  2. 收据对象
  3. 处理器和格式化程序
  4. 打印机(仅用于打印机处理器)(可能表示电子邮件服务器依赖,但我认为这个例子相当清楚)。

通过使用Autofac.Core.ResolvedParameter,我们可以引用其他注册对象(按名称)。 这将保持注册时间长,但持平。 任何层次结构都表示(浅 – 如果这是一个单词)仅作为父子,并且非常可重用。 Resolve仅在根组合对象上调用 – 3个收据引擎(标准,礼品和电子邮件)。 对于每个根组合对象,整个依赖关系链现在都是可解析的。