注册的概念
通过Autofac创建ContainerBuilder并且告知builder哪个组件实现哪项服务的方式注册组件。
组件可以通过反射(通过注册一个指定的.NET类型或者开放的类)创建;或者通过提供一个提供一个已经创建的实例(你创建的一个对象的实例);亦或通过lambda表达式(执行匿名函数初始化你的对象)。
ContainerBuilder有一系列的Register()方法实现上述功能。
单个组件实现一个或者多个服务的话可以通过ContainerBuilder的As()
方法串起来
// 创建注册组件/服务的builder.
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解析服务, 例如这句将解析通过
// lambda表达式注册的IConfigReade服务
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
。这时,Autofac就会选择第二个构造函数,因为这个构造函数的参数个数是容器里能找到的最多的一个。
关于基于反射组件的重要说明:通过RegisterType
注册的任何组件都必须是一个具体类型。尽管组件可以指明抽象类或者接口作为服务,但是不能注册一个抽象/接口组件。稍微想一下它的实现原理就明白了,Autofac创建注册的组件的实例。你不能new一个抽象类或者一个接口,必须提供一个实现,对吧。
指定一个构造函数
你可以手动选择一个制定的构造函数来替换你注册的组件时自动选择的构造函数,通过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类型的构造函数参数,而B可能还有额外的依赖。
表达式创建组件指向的服务是由该表达式自动判断的。
下面是一些例子,这些例子说明了反射组件的创建不能很好的定位其需求而lambda表达式则能很好的定位。
复杂参数
构造函数参数不可能总是由简单的常量定义的。使用下面的代码比疑惑怎样通过XML配置语法构造一个指定类型要清晰的多:
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
(当然,你可能要在配置文件里指定会话的过期时间,不过这不重要,你已经掌握了要点)
属性注入
Autofac提供了更一流的方式实现属性注入,你可以使用表达式和属性初始化工具来设置属性,例如:
builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });
方法ResolveOptional
会尝试解析值,而且如果服务未注册也不会抛出异常。(如果服务注册了但是不能被解析还是会抛异常的)。这是解析服务的选项之一。
属性注入在下面的情况下不推荐使用。例如空对象模式,重载构造函数,构造函数有默认值或者含有使用构造函数注入可选依赖性的“不可变”组件。
通过参数值实现的选择
隔离组件创建的一个巨大有点是具体类型可能是可变的。这种情况经常发生在运行时,而不是在配置时:
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
,创建哪个类由运行时提供的账户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>
) ,这个类型就会覆盖公开的泛型版本。
服务 vs. 组件
当你注册组件时,你必须告诉Autofc这个组件是属于哪个服务的。默认情况下,大多数注册通常以自己作为组件的服务类型:
// 服务就是"CallLogger"
builder.RegisterType<CallLogger>();
组件只能被解析为他们指定的服务,以上面的例子来说:
// 正确,因为它的服务就是CallLogger:
scope.Resolve<CallLogger>();
// 不正确,因为未告知容器CallLogger的服务类型是ILogger:
scope.Resolve<ILogger>();
如果你想,你可以给一个组件指定多个服务:
builder.RegisterType<CallLogger>()
.As<ILogger>()
.As<ICallInterceptor>();
一旦你给组件指定了服务,你可以基于服务解析组件。注意,一旦你给组件指定了某个服务,默认的服务(组件类型)就会被覆盖:
// 都能工作,因为在注册的时候指定了这些服务:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();
// 不能工作,因为已经指定了服务类型
// service overrides on the component:
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()
注册的配置
可以使用XML或者可编程配置来提供成组的注册或者在运行时改变注册信息。也可以用Autofac模块实现动态注册生成或者条件注册逻辑。
动态注册
Autofac模块是最简单的实现动态注册逻辑或者简单交叉功能的方法。例如,你可以使用模块来动态附加一个log4net日志记录器实例到一个已经解析的服务上。
如果你发现你需要更多的动态行为,例如为一个新的隐式关系类型提供支持,你可以查看高级内容区的注册的源代码
注册参数
注册组件时可以设置用于解析服务解析该组件时使用的参数集。(如果想在解析时提供参数,可以这么做
参数类型
Autofac根据匹配策略提供了若干不同的参数类型:
名字参数
-通过名字匹配目标参数类型参数
-通过参数类型匹配解析参数
-灵活的参数匹配
名字参数
和类型参数
只能用常量值
解析参数
可以通过从容器中动态解析的方式获取,例如在通过名字解析服务时。
反射组件的参数
注册一个反射组件时,构造函数的参数可能无法通过容器解析到。这时可以通过注册来提供参数。
例如有一个配置读取器需要构造配置段名字构造:
public class ConfigReader : IConfigReader
{
public ConfigReader(string configSectionName)
{
// Store config section name
}
// ...read configuration based on the section name.
}
可以通过lambda表达式注册来设置:
builder.Register(c => new ConfigReader("sectionName")).As<IConfigReader>();
或者传递参数到反射组件注册:
// Using a NAMED parameter:
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter("configSectionName", "sectionName");
// Using a TYPED parameter:
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter(new TypedParameter(typeof(string), "sectionName"));
// Using a RESOLVED parameter:
builder.RegisterType<ConfigReader>()
.As<IConfigReader>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(string) && pi.Name == "configSectionName",
(pi, ctx) => "sectionName"));
Lambda表达式组件的参数
lambda表达式组件注册时,除了注册时设置参数值以外,还可以在服务解析时设置参数
在组件注册表达式里通过匿名参数委托设置参数。输入IComponentContext
和IEnumerable<Parameter>
来代替仅输入IComponentContext
// Use TWO parameters to the registration delegate:
// c = The current IComponentContext to dynamically resolve dependencies
// p = An IEnumerable<Parameter> with the incoming parameter set
builder.Register((c, p) =>
new ConfigReader(p.Named<string>("configSectionName")))
.As<IConfigReader>();
通过参数解析时,lambda表达式会用到传入的参数值
var reader = scope.Resolve<IConfigReader>(new NamedParameter("configSectionName", "sectionName"));
属性和方法注入
除了在组件创建的时候通过构造函数参数注入这个首选方法外,你还可以使用属性或者方法注入来提供依赖的组件。
属性注入使用可写属性实现注入。方法注入通过调用方法设置依赖
属性注入
如果这个对象是一个Lambda表达式对象,使用一个对象初始化方法
builder.Register(c => new A { B = c.Resolve<B>() });
builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());
如果组件是一个反射对象,则可以使用PropertiesAutowired()
扩展方法注入属性
builder.RegisterType<A>().PropertiesAutowired();
如果你需要指定某个特定的属性和值,可以使用WithPropery()
扩展方法
builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);
方法注入
使用方法给某个组件设置注入值的最简单方法就是使用Lambda表达式组件并且在注册时调用这个方法:
builder.Register(c => {
var result = new MyObjectType();
var dep = c.Resolve<TheDependency>();
result.SetTheDependency(dep);
return result;
});
如果不能使用Lambda注册的话,可以添加一个激活事件:
builder
.Register<MyObjectType>()
.OnActivating(e => {
var dep = e.Context.Resolve<TheDependency>();
e.Instance.SetTheDependency(dep);
});
程序集扫描
Autofac在程序集中可以使用一个约定来查找和注册组件。你可以扫描和注册单独的类型或者指定扫描Autofac模块
针对类型的扫描
换句话说就是基于约定的注册和扫描,Autofac从程序集中按照用户指定的规则注册一系列的类型:
var dataAccess = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
每次RegisterAssemblyTypes()
调用将只接受一个规则,如果有多种不同类型的组件要注册的话,需要调用多次RegisterAssemblyTypes()
。