Autofac官方文档(三)【注册组件之注册概念】

通过创建一个ContainerBuilder并通知构建器哪些组件暴露哪些服务,您可以使用Autofac注册组件。

组件可以通过反射(通过注册一个特定的.NET类型或打开通用)来创建; 通过提供一个现成的实例(一个你创建的对象的实例); 或者通过lambda表达式(一个执行实例化对象的匿名函数)。 ContainerBuilder有一系列的Register()方法,允许你设置它们。

每个组件都暴露一个或多个使用ContainerBuilder上的As()方法连接的服务。

//创建与哪个组件/服务注册的构建器。
var builder = new ContainerBuilder();

//注册暴露接口的类型...
builder.RegisterType<ConsoleLogger>().As<ILogger>();

//注册您创建的对象的实例...
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

//注册执行以创建对象的表达式...
builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>();

//构建容器来完成注册并准备对象解析。
var container = builder.Build();

//现在您可以使用Autofac解决服务问题。例如,这一行将执行注册到IConfigReader服务的lambda表达式。
using(var scope = container.BeginLifetimeScope())
{
  var reader = container.Resolve<IConfigReader>();
}

反射组件

按类型注册

通过反射生成的组件通常按类型进行注册:

var builder = new ContainerBuilder();
builder.RegisterType<ConsoleLogger>();
builder.RegisterType(typeof(ConfigReader));

当使用基于反射的组件时,Autofac会自动使用您的类的构造函数,其中的大部分参数可以从容器中获得。

例如,假设你有一个有三个构造函数的类,像这样:

public class MyComponent
{
    public MyComponent() { /* ... */ }
    public MyComponent(ILogger logger) { /* ... */ }
    public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ }
}

现在说你在你的容器中注册组件和服务,如下所示:

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
builder.RegisterType<ConsoleLogger>().As<ILogger>();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  var component = container.Resolve<MyComponent>();
}

当你解决你的组件时,Autofac会看到你已经注册了一个ILogger,但是你没有注册一个IConfigReader。在这种情况下,将选择第二个构造函数,因为这是容器中可以找到的参数最多的构造函数。

关于基于反射的组件的一个重要说明:通过RegisterType注册的任何组件类型都必须是具体的类型。虽然组件可以将抽象类或接口作为服务公开,但不能注册抽象/接口组件。如果你仔细想一想,幕后的Autofac正在创建你正在注册的东西的一个实例。你不能“抽象出”抽象类或接口。你必须有一个实施,对吧?

指定一个构造函数

您可以手动选择一个特定的构造函数来使用并覆盖自动选择,方法是使用UsingConstructor方法和代表构造函数中的参数类型的类型列表注册组件:

builder.RegisterType<MyComponent>()
       .UsingConstructor(typeof(ILogger), typeof(IConfigReader));

请注意,您仍然需要在解析时提供必要的参数,否则在尝试解析对象时会出现错误。 您可以在注册时传递参数,也可以在解决时间传递参数。

实例组件

在某些情况下,您可能需要预先生成对象的实例并将其添加到容器以供注册的组件使用。 您可以使用RegisterInstance方法执行此操作:

var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

当你这样做的时候需要考虑的事情是,Autofac自动处理注册组件的处理,你可能想要自己控制生命周期,而不是让Autofac调用Dispose在你的对象上。 在这种情况下,您需要使用ExternallyOwned方法注册实例:

var output = new StringWriter();
builder.RegisterInstance(output)
       .As<TextWriter>()
       .ExternallyOwned();

Autofac集成到现有的单例实例已经存在且需要由容器中的组件使用的应用程序中时,注册提供的实例也很方便。 而不是直接将这些组件绑定到单例,它可以作为一个实例注册到容器中:

builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();

这确保了静态单例最终可以被淘汰,并替换为一个容器管理的单例。

实例公开的默认服务是实例的具体类型。 请参阅下面的“服务与组件”。

Lambda表达式组件

反射是组件创建的一个很好的默认选择。事情变得混乱,但是,组件创建逻辑超越了一个简单的构造函数调用。

Autofac可以接受委托或lambda表达式作为组件的创建者:

builder.Register(c => new A(c.Resolve<B>()));

提供给表达式的参数c是在其中创建组件的组件上下文(IComponentContext对象)。 您可以使用它来解析容器中的其他值以协助创建组件。 使用这个而不是一个闭包来访问容器是非常重要的,这样可以正确地支持确定性的处理和嵌套容器。

使用此上下文参数可以满足其他依赖关系 - 在示例中,A需要类型B的构造函数参数,可能有其他依赖项。

表达式创建的组件提供的默认服务是表达式的推断返回类型。

下面是反射组件创建遇到的需求的一些例子,但很好地解决了lambda表达式。

复杂的参数

构造函数参数不能总是用简单的常量值声明。而不是疑惑如何使用XML配置语法来构造某种类型的值,请使用以下代码:

builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

(当然,会话过期可能是你想要在配置文件中指定的东西 - 但你得到的要点))

属性注入

虽然Autofac提供了更高级的属性注入方法,但您也可以使用表达式和属性初始值设定项来填充属性:

builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });

ResolveOptional方法将尝试解析该值,但不会在服务未注册时抛出异常。 (如果服务已经注册但是无法正确解析,您仍然会收到异常。)这是解决服务的选项之一。

大多数情况下不建议进行属性注入。 像Null对象模式,重载的构造函数或构造函数参数的默认值等替代方案可以使用构造函数注入来创建更清洁,具有可选依赖性的“不可变”组件。

用参数值选择实现

隔离组件创建的一大优点是具体类型可以变化。 这通常在运行时完成,而不仅仅是配置时间:

builder.Register<CreditCard>(
  (c, p) =>
    {
      var accountId = p.Named<string>("accountId");
      if (accountId.StartsWith("9"))
      {
        return new GoldCard(accountId);
      }
      else
      {
        return new StandardCard(accountId);
      }
    });

在这个例子中,CreditCardGoldCardStandardCard两个类实现 - 实例化哪个类取决于运行时提供的账户ID。

在本例中,参数通过一个可选的第二个参数p提供给创建函数。

使用此注册将看起来像:

var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));

如果声明创建CreditCard实例的委托并使用委托工厂,则可以实现干净的,类型安全的语法。

打开通用组件

Autofac支持开放的泛型类型。 使用RegisterGeneric()构建器方法:

builder.RegisterGeneric(typeof(NHibernateRepository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

当从容器请求一个匹配的服务类型时,Autofac将把它映射到一个等效的实现类型的关闭版本:

//Autofac将返回一个`NHibernateRepository<Task>`
var tasks = container.Resolve<IRepository<Task>>();

注册专门的服务类型(例如IRepository<Person>)将覆盖开放的通用版本。

服务与组件

当你注册组件时,你必须告诉Autofac该组件暴露哪些服务。 默认情况下,大多数注册只会显示为注册类型:

//这暴露了服务“CallLogger”
builder.RegisterType<CallLogger>();

组件只能通过它们公开的服务来解决。 在这个简单的例子中,它表示:

// 这将工作,因为组件默认显示类型
scope.Resolve<CallLogger>();

// 这是行不通的,因为我们没有告诉注册,也暴露了CallLogger上的ILogger接口:
scope.Resolve<ILogger>();

您可以使用任意数量的服务来公开一个组件:

builder.RegisterType<CallLogger>()
       .As<ILogger>()
       .As<ICallInterceptor>();

一旦公开服务,就可以根据该服务解析组件。 但请注意,一旦将组件公开为特定服务,将覆盖默认服务(组件类型):

//这些都是可行的,因为我们在注册中公开了相应的服务:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();

//这将不再适用,因为我们在组件上指定了服务覆盖:
scope.Resolve<CallLogger>();

如果要将组件公开为一组服务以及使用默认服务,请使用AsSelf方法:

builder.RegisterType<CallLogger>()
       .AsSelf()
       .As<ILogger>()
       .As<ICallInterceptor>();

现在所有这些都将起作用:

//这些都是可行的,因为我们在注册中公开了相应的服务:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();
scope.Resolve<CallLogger>();

Default Registrations

如果多个组件公开相同的服务,Autofac将使用最后一个注册组件作为该服务的默认提供者:

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();

在这种情况下,FileLogger将是ILogger的默认值,因为它是最后一个注册的。

要覆盖此行为,请使用PreserveExistingDefaults() 修改:

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();

有条件注册

Autofac 4.4.0引入了条件注册

在大多数情况下,如上面的部分所述,覆盖注册“默认注册”足以在运行时解析正确的组件。 确保事物以正确的顺序注册; 使用PreserveExistingDefaults(); 并利用lambda/delegate 注册更复杂的条件和行为可以让你走很远。

可能有一些情况,这可能不是你想要的方式:

  • 如果别的东西在处理这个功能,你不希望这个组件出现在系统中。 例如,如果您解析了一个服务的IEnumerable <T>,则无论您是否使用PreserveExistingDefaults(),都将返回实现该服务的所有已注册组件。 通常这很好,但是有一些边缘情况你可能不想要。

    -如果其他组件没有注册,您只想注册组件; 或者只有在注册了其他组件的情况下。 您无法从正在构建的容器中解析出所有内容,也不应该更新已经构建的容器。 能够根据其他注册条件注册组件可能会有帮助。

There are two registration extensions that can help in these cases:
有两个注册扩展可以帮助在这些情况下:

  • OnlyIf() - 提供一个使用IComponentRegistrylambda确定是否应该进行注册。

  • IfNotRegistered() - 如果其他服务已经注册,则停止注册。

这些扩展在ContainerBuilder.Build()时运行,并将按照实际组件注册的顺序执行。 以下是一些示例显示它们的工作原理:

var builder = new ContainerBuilder();

// (the RegisterType<T>).只有ServiceA将被注册。
//注意`NotRegistered`将服务类型设为检查(As<T>),而不是组件类型(RegisterType<T>)。
builder.RegisterType<ServiceA>()
       .As<IService>();
builder.RegisterType<ServiceB>()
       .As<IService>()
       .IfNotRegistered(typeof(IService));


// HandlerA 将被注册 - 它正在运行之前HandlerB有机会被注册,因此IfNotRegistered检查不会找到它。


// HandlerC将不会被注册,因为它在AFTER HandlerB之后运行。 注意它可以检查类型“HandlerB”,因为HandlerB注册了AsSelf()而不仅仅是As<IHandler>()。 同样,IfNotRegistered只能检查“As”类型。

builder.RegisterType<HandlerA>()
       .AsSelf()
       .As<IHandler>()
       .IfNotRegistered(typeof(HandlerB));
builder.RegisterType<HandlerB>()
       .AsSelf()
       .As<IHandler>();
builder.RegisterType<HandlerC>()
       .AsSelf()
       .As<IHandler>()
       .IfNotRegistered(typeof(HandlerB));

//Manager将被注册,因为IService和HandlerB都被注册。 OnlyIf谓词可以提供更多的灵活性。
builder.RegisterType<Manager>()
       .As<IManager>()
       .OnlyIf(reg =>
         reg.IsRegistered(new TypedService(typeof(IService))) &&
         reg.IsRegistered(new TypedService(typeof(HandlerB))));

//这是条件实际运行的时候。再次,他们按照注册添加到`ContainerBuilder`的顺序运行。
var container = builder.Build();

配置注册

您可以使用XML或编程配置(“模块”)将注册组合在一起或在运行时更改注册。 您也可以使用Autofac模块进行一些动态注册生成或条件注册逻辑。

动态提供的注册

Autofac模块是引入动态注册逻辑或简单交叉功能的最简单的方法。 例如,您可以使用模块将log4net记录器实例动态附加到正在解析的服务。

如果您发现需要更多的动态行为,例如添加对新的隐式关系类型的支持,则可能需要查看高级概念区域中的注册源码部分。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值