你的位置:首页 > ASP.net教程

[ASP.net教程][翻译] Autofac 中注册的概念


原文链接:http://docs.autofac.org/en/latest/register/registration.html

所谓注册组件,是指创建 ContainerBuilder 的实例,并告诉它哪些组件暴露哪些服务。

组件可以用反射创建,可以提供已经创建好的对象的实例,还可以用拉姆达表达式创建。ContainerBuilder 有一组 Register 方法来进行装配。

每个组件暴露一到多个服务,这些服务用生成器的 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 服务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();

这样就消除了组件对单件的引用,改由容器提供。

实例暴露的默认服务是实例的具体类型,参考“服务和组件”一节。

拉姆达表达式组件

反射是创建组件的好方式。但是,如果创建逻辑超出简单的调用构造函数时,反射就不够用了。

Autofac 可以接受一个创建组件的委托或拉姆达表达式:

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

参数 c 是组件上下文(IComponentContext),组件在此上下文中注册。可以用它从容器解析出其他值,来辅助创建组件。应使用组件上下文,而不是闭包来访问容器,这很要紧,只有这样资源清理和嵌套容器才不会出问题。

额外的依赖可以用此上下文参数来满足,例如, A的构造函数需要B类型的参数,并且 B 可能有其他依赖项。

表达式创建的组件暴露的默认服务是从表达式推断出的返回类型。

以下是反射方式不能胜任,但拉姆达表达式工作良好的例子。

复杂参数

构造函数参数不能总是声明为简单常量。与其为使用

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

(当然, session 过期时间在配置文件中更好 – 这里只是说明个大概)

属性注入

尽管有更好的属性注入方式,仍然可以使用表达式和属性初始化器来组装属性:

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

ResolveOptional 方法尝试解析值,即使服务没有注册,也不会抛出异常。(如果服务已注册但不能正确解析仍然会抛出异常) 这是解析服务的选项之一。

多数情况下不推荐属性注入。如果组件有可选的依赖项,通过空对象模式, 重载构造函数,或构造函数参数默认值等替代方案,就可以用构造函数注入的方式来创建整洁的,“稳定的(immutable)”组件。

通过参数值选择实现类

把组件的创建动作隔离出来后,依赖项的具体类型可以变换,这是一个很大的好处。变换通常在运行时完成,而不单是配置时:

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

本例,CreditCard 有两个实现类,GoldCard 和 StandardCard,使用哪个类是在运行时由 accountId 决定的。

例子里的第二个参数名为 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>();// 有问题,因为注册时没有将 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>();

默认注册

如果多个组件暴露相同的服务,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();

这时,ConsoleLogger 将是默认的 ILogger 因为后面注册的 FileLogger 使用了 PreserveExistingDefaults()。

配置

可使用 Autofac modules 还可实现注册信息动态生成,或实现条件注册逻辑。

动态注册

Autofac modules 是引入动态注册逻辑或简单正交特性的最简单方式。例如,将 log4net logger 实例动态附加到正在解析的服务。

如果需要更加动态的行为,比如添加隐式关联类型支持,请参考check out the registration sources section in the advanced concepts area.