Autofac 属性和方法注入
虽然构造函数参数注入是向正在构建的组件传递值的首选方法,但也可以使用属性或方法注入来提供值。
属性注入 使用可写属性而不是构造函数参数进行注入。方法注入 通过调用方法设置依赖项。
属性注入
如果组件是一个 lambda 表达式组件 ,则使用对象初始化器:
builder.Register(c => new A { B = c.Resolve<B>() });
builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());
必需属性
从 Autofac 7.0 开始,对于 反射组件 ,所有 必需属性 在对象构造时会自动解析,并通常与强制性的构造函数参数以相同方式处理。
例如,考虑以下类型:
public class MyComponent
{
public required ILogger Logger { protected get; init; }
public required IConfigReader ConfigReader { protected get; init; }
public IDatabaseContext Context { get; set; }
}
当组件被解析时,Autofac 将填充 Logger
和 ConfigReader
属性,就像它们是构造函数参数一样。Context
属性将被视为标准属性,不会默认被填充。
你可以使用任何有效的访问修饰符组合在必需属性上,但是 public required ... { protected get; init; }
在这些示例中被使用,因为它提供了类似于构造函数的访问和可见性:属性只在构造时可设置,对其他类不可见。
必需属性注入也自动适用于所有具有必需属性的基类:
public class ComponentBase
{
public required ILogger Logger { protected get; init; }
}
public class MyComponent : ComponentBase
{
public required IConfigReader ConfigReader { protected get; init; }
}
在上述示例中,解析 MyComponent
时,将同时在基类和组件本身中填充 Logger
。
重要 Autofac 并不认为必需属性的类型是否为
nullable
表示某种 “可选” 必需属性。如果属性标记为required
,那么它是必需的,并且必须注入,或者通过参数提供,无论其是否为null
。
必需属性与构造函数
如果你想混合使用构造函数和必需属性,可以这样做:
public class MyComponent
{
public MyComponent(ILogger logger)
{
Logger = logger;
}
private ILogger Logger { get; set; }
public required IConfigReader ConfigReader { protected get; init; }
}
如果有多个构造函数可用,默认情况下 Autofac 会选择参数匹配最多的构造函数(除非使用了 自定义构造函数选择 。这种情况保持不变,必需属性集对选择的构造函数没有影响。
Autofac 并不知道你是否在构造函数内部设置了某个必需属性。看这个例子:
public class MyComponent
{
public MyComponent()
{
}
public MyComponent(ILogger logger)
{
Logger = logger;
}
public required ILogger Logger { protected get; init; }
}
在这种情况下,Autofac 会选择接受 ILogger
参数的构造函数,该构造函数又设置了 Logger
属性。然而,由于 Logger
标记为必需属性,Autofac 将再次解析 ILogger
并将其注入到必需属性中。
要避免这种情况,请使用 SetsRequiredMembers 属性标记设置所有必需属性的构造函数:
using System.Diagnostics.CodeAnalysis;
public class MyComponent
{
public MyComponent()
{
}
[SetsRequiredMembers]
public MyComponent(ILogger logger)
{
Logger = logger;
}
public required ILogger Logger { protected get; init; }
}
由于构造函数标记为设置了所有必需成员,当使用该构造函数创建组件实例时,Autofac 将不会进行任何必需属性注入。
必需属性与参数
在 注册参数 或 解析参数 时提供的任何 TypedParameter
都将用于注入必需属性。但是,NamedParameter
和 PositionalParameter
不被视为属性注入的有效参数,因为它们被认为是只应用于构造函数参数。
PropertiesAutowired
您可以在注册时使用 PropertiesAutowired()
修饰符在任何组件上注入属性:
// 默认行为:注入所有公共可写的属性。
builder.RegisterType<A>().PropertiesAutowired();
// 提供更精细的属性选择器委托。此示例显示注入所有属性,其中属性类型以'I'开头——一种“仅注入接口属性”的方式。委托接收要注入的属性的PropertyInfo描述和注入的对象。
builder.RegisterType<B>()
.PropertiesAutowired(
(propInfo, instance) => propInfo.PropertyType.Name.StartsWith("I"));
// 更加复杂,您可以提供自己的IPropertySelector实现,包含所需的任何功能。别忘了这将在每个关联的解决期间运行,因此性能很重要!
builder.RegisterType<C>().PropertiesAutowired(new MyCustomPropSelector());
手动指定属性
如果您只想将一个特定的属性和值连接起来,可以使用 WithProperty()
修饰符:
builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);
覆盖必需属性
使用 WithProperty
方法在注册类型时为必需属性提供的任何属性值将覆盖注入该属性的需求,Autofac 将使用提供的值代替:
public class MyComponent
{
public required ILogger Logger { protected get; init; }
public required IConfigReader ConfigReader { protected get; init; }
}
var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>().WithProperty("Logger", new ConsoleLogger());
var container = builder.Build();
// 尽管没有注册ILogger,但这不会抛出异常。Logger属性由WithProperty提供。
container.Resolve<MyComponent>();
在现有对象上注入属性
您还可以仅填充对象上的属性。为此,请使用生命周期范围上的 InjectUnsetProperties
扩展,它将解析并填充所有**公共、可写且尚未设置(null
)**的属性:
lifetimeScope.InjectUnsetProperties(myObject);
方法注入
最简单的方法是在组件上调用一个方法来设置值,只需使用一个 lambda 表达式组件 并在激活器中处理方法调用:
builder.Register(c => {
var result = new MyObjectType();
var dep = c.Resolve<TheDependency>();
result.SetTheDependency(dep);
return result;
});
如果不能使用注册 lambda,可以在 激活事件处理器 中添加一个:
builder
.RegisterType<MyObjectType>()
.OnActivating(e => {
var dep = e.Context.Resolve<TheDependency>();
e.Instance.SetTheDependency(dep);
});