我正在修改现有的应用程序以使用Autofac Property Injection。似乎无论我使用哪种方法来注册具有属性的类型,除非它们具有公共setter,否则属性始终为null。使用其他IoC容器(例如Structuremap),可以使用程序集内部设置范围,并使用程序集上的InternalsVisibleTo
属性使其可用。限制客户端修改分配似乎很好。
这可以通过Autofac实现吗?或者在使用属性注入时是否有另一种方法来保证分配安全?
我尝试使用PropertiesAutoWired()
的反射以及从我的WebApi Global.asax解析.WithParameter()
- 指定要设置的特定参数,但没有成功作为内部设置器。
[assembly: InternalsVisibleTo("MyWebAPI.dll")]
[assembly: InternalsVisibleTo("Autofac.dll")]
[assembly: InternalsVisibleTo("Autofac.Configuration.dll")]
namespace My.Namespace
{
public class BaseContext
{
public MyPublicClass _dbHelper { get; internal set; }
public BaseContext()
{
}
protected string DbConnectionString
{
get
{
return _dbHelper.DbConn; //<-Always null unless setter is public
}
}
}
}
答案 0 :(得分:5)
您不能使用autofac注入internal
setter,因为AutowiringPropertyInjector
类只查找公共属性(请参阅source)。
然而AutowiringPropertyInjector
中的逻辑非常简单,因此您可以创建自己的版本,为非公共属性注入:
public static class AutowiringNonPublicPropertyInjector
{
public static void InjectProperties(IComponentContext context,
object instance, bool overrideSetValues)
{
if (context == null)
throw new ArgumentNullException("context");
if (instance == null)
throw new ArgumentNullException("instance");
foreach (
PropertyInfo propertyInfo in
//BindingFlags.NonPublic flag added for non public properties
instance.GetType().GetProperties(BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic))
{
Type propertyType = propertyInfo.PropertyType;
if ((!propertyType.IsValueType || propertyType.IsEnum) &&
(propertyInfo.GetIndexParameters().Length == 0 &&
context.IsRegistered(propertyType)))
{
//Changed to GetAccessors(true) to return non public accessors
MethodInfo[] accessors = propertyInfo.GetAccessors(true);
if ((accessors.Length != 1 ||
!(accessors[0].ReturnType != typeof (void))) &&
(overrideSetValues || accessors.Length != 2 ||
propertyInfo.GetValue(instance, null) == null))
{
object obj = context.Resolve(propertyType);
propertyInfo.SetValue(instance, obj, null);
}
}
}
}
}
现在您可以在OnActivated
事件
var builder = new ContainerBuilder();
builder.RegisterType<MyPublicClass>();
builder.RegisterType<BaseContext>()
.OnActivated(args =>
AutowiringNonPublicPropertyInjector
.InjectProperties(args.Context, args.Instance, true));
然而,上面列出的解决方案现在注入了所有类型的属性,所以即使是私有属性和受保护的属性也是如此,因此您可能需要通过一些额外的检查来扩展它,以确保您只注入您期望的属性。
答案 1 :(得分:0)
我正在使用这样的解决方案:
builder.RegisterType<MyPublicClass>();
builder.RegisterType<BaseContext>()
.OnActivating(CustomPropertiesHandler);
使用这样的处理程序:
//If OnActivated: Autofac.Core.IActivatedEventArgs
public void CustomPropertiesHandler<T>(Autofac.Core.IActivatingEventArgs<T> e)
{
var props = e.Instance.GetType()
.GetTypeInfo().DeclaredProperties //Also "private prop" with "public set"
.Where(pi => pi.CanWrite) //Has a set accessor.
//.Where(pi => pi.SetMethod.IsPrivate) //set accessor is private
.Where(pi => e.Context.IsRegistered(pi.PropertyType)); //Type is resolvable
foreach (var prop in props)
prop.SetValue(e.Instance, e.Context.Resolve(prop.PropertyType), null);
}
由于IActivatingEventArgs和IActivatedEventArgs都有实例和上下文,因此您可能希望使用在CustomPropertiesHandler上使用这些参数的包装方法。
答案 2 :(得分:0)
我们也可以将@nemesv实现编写为扩展方法。
public static class AutofacExtensions
{
public static void InjectProperties(IComponentContext context, object instance, bool overrideSetValues)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
foreach (var propertyInfo in instance.GetType().GetProperties(BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic))
{
var propertyType = propertyInfo.PropertyType;
if ((!propertyType.IsValueType || propertyType.IsEnum) && (propertyInfo.GetIndexParameters().Length == 0) && context.IsRegistered(propertyType))
{
var accessors = propertyInfo.GetAccessors(true);
if (((accessors.Length != 1) ||
!(accessors[0].ReturnType != typeof(void))) &&
(overrideSetValues || (accessors.Length != 2) ||
(propertyInfo.GetValue(instance, null) == null)))
{
var obj = context.Resolve(propertyType);
propertyInfo.SetValue(instance, obj, null);
}
}
}
}
public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InjectPropertiesAsAutowired<TLimit, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registration)
{
return registration.OnActivated(args => InjectProperties(args.Context, args.Instance, true));
}
使用;
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<StartupConfiguration>().As<IStartupConfiguration>().AsSelf().InjectPropertiesAsAutowired().AsImplementedInterfaces().SingleInstance();
}
答案 3 :(得分:0)
当前version of Autofac为[{1}} IPropertySelector
定义的PropertiesAutowired
参数,用于filter out injectable properties。
IPropertySelector
的默认实施是DefaultPropertySelector,它过滤非公共属性。
public virtual bool InjectProperty(PropertyInfo propertyInfo, object instance)
{
if (!propertyInfo.CanWrite || propertyInfo.SetMethod?.IsPublic != true)
{
return false;
}
....
}
定义自定义IPropertySelector
,允许注入非公共属性
public class AccessRightInvariantPropertySelector : DefaultPropertySelector
{
public AccessRightInvariantPropertySelector(bool preserveSetValues) : base(preserveSetValues)
{ }
public override bool InjectProperty(PropertyInfo propertyInfo, object instance)
{
if (!propertyInfo.CanWrite)
{
return false;
}
if (!PreserveSetValues || !propertyInfo.CanRead)
{
return true;
}
try
{
return propertyInfo.GetValue(instance, null) == null;
}
catch
{
// Issue #799: If getting the property value throws an exception
// then assume it's set and skip it.
return false;
}
}
}
使用
builder.RegisterType<AppService>()
.AsImplementedInterfaces()
.PropertiesAutowired(new AccessRightInvariantPropertySelector(true));
<强>替代地强>
安装的
PM> Install-Package Autofac.Core.NonPublicProperty
使用
builder.RegisterType<AppService>()
.AsImplementedInterfaces()
.AutoWireNonPublicProperties();