Autofac子依赖项链注册
本文关键字:注册 项链 依赖 Autofac | 更新日期: 2023-09-27 18:25:04
问题
如何构造AutoFac ContainerBuilder,以便正确解析我的子依赖项(假设一个接口有多个具体实现)?默认注册/解析将不起作用,因为我有多个子依赖项的具体实现,并且子依赖项的解析取决于主对象的解析。在所有情况下,我都希望使用构造函数注入。
场景
例如,假设我需要打印收据,所以我创建了一个名为IReceipt的接口,其中有一个方法,名为PrintReceipt()。
但是我需要打印3种收据
- 标准收据
- 礼品收据
- 电子邮件回执
所有收据类型都有不同的格式,电子邮件收据根本不打印,而是通过电子邮件发送的。所以我希望我的IReceipt能够依赖于格式化程序和处理器。假设我发明了IProcessor,方法为Process(),它可以是打印机处理器,也可以是电子邮件处理器(打印机处理器负责打印机通信,电子邮件处理器负责与SMTP服务器通信)。此外,我创建了一个IFormatter,它可以将输入馈送到处理器对象,格式化为标准收据、礼品收据,甚至是电子邮件收据的HTML。因此,IRecept的任何具体实现上的构造函数现在都需要两个依赖项——一个IFormatter和一个IProcessor。
现在,在我需要决定的根本组成部分,我是在解决用于标准收据、礼品收据还是电子邮件收据的IReceipt。我想调用传递必要参数的容器Resolve()方法,以便它解析正确的IReceipt。此外,我希望注册ContainerBuilder知道,如果我试图解决IReceipt标准收据的具体实现,它需要使用正确的标准收据I格式器和正确的标准回执I处理器来解决子依赖项。礼品收据场景和电子邮件收据场景也是如此。
回顾
因此,在所有这些中,我的问题是,我如何构建ContainerBuilder,以便在设计时定义子依赖项,并且对Resolve()的单个调用将正确识别所需的具体实现?我不是在寻求与打印机交谈或发布HTML的解决方案。这个问题严格适用于Autofac注册和解决方法。在另一个客户,我使用了CastleWindsor的确切策略,但我现在的客户使用的是Autofac。
好的,所以我找到了一种执行子依赖关系链接的方法。同样,我们希望每个根组合对象调用resolve一次,并使所有子依赖项都满足整个链。不想卷入关于最佳实践的宗教辩论——是否应该实施工厂方法、服务定位器等。我故意省略了IoC范围界定,因为它不是我最初问题的主题。
这个人为的例子没有实现电子邮件或打印机功能,但现在已经过时了。重点是展示如何预先定义整个依赖链。现在自动化的单元测试将更容易实现。
代码
主程序
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();
}
}
}
接口
IP打印机
namespace MyAutoFacTest
{
public interface IPrinter
{
void Print();
}
}
IReceipt
namespace MyAutoFacTest
{
public interface IReceipt
{
void PrintReceipt(int id);
}
}
I处理器
namespace MyAutoFacTest
{
public interface IProcessor
{
void Process(string formattedString);
}
}
I格式化程序
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);
}
}
}
结论
与需要创建的对象相关联的大多数噪声被绑定到一个位置。在实践中,我可能会将注册移动到它自己的代码块(也许是一个静态类)。将噪声排除在函数类之外会产生非常干净的代码,并将重点放在函数意图上。所有的电线都在这个过程的早期,现在已经不碍事了。
特别是在注册方面,我设计的示例有4层依赖关系。。
- Main
- Receipt对象
- 处理器和格式化程序
- 打印机(仅在打印机处理器上使用)(可能表示对电子邮件服务器的依赖,但我认为这个例子非常清楚)
通过使用Autofac.Core.ResolvedParameter,我们可以引用其他注册的对象(按名称)。这将使注册时间长,但保持不变。任何层次结构都只表示为父子关系,并且非常可重用。Resolve只在根组合对象上调用,即3个收据引擎(Standard、Gift和Email)。对于每个根组合对象,整个依赖关系链现在都是可解析的。