【问题标题】:Property Injection with internal setter使用内部设置器进行属性注入
【发布时间】:2017-11-02 20:26:12
【问题描述】:

我有一个现有的应用程序,我正在修改它以使用 Autofac 属性注入。似乎无论我使用哪种方法向属性注册我的类型,属性始终为空,除非它们具有公共设置器。使用其他 IoC 容器(例如 Structuremap),可以在内部设置 setter 并使用程序集上的 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
            }
        }
    }
}

【问题讨论】:

    标签: properties autofac internal


    【解决方案1】:

    您不能使用 autofac 注入 internal 设置器,因为 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));
    

    但是,上面列出的解决方案现在注入了所有类型的属性,甚至是私有和受保护的属性,因此您可能需要通过一些额外的检查来扩展它,以确保您只会注入您期望的属性。

    【讨论】:

      【解决方案2】:

      我正在使用这样的解决方案:

      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 使用这些参数。

      【讨论】:

        【解决方案3】:

        我们还可以将@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();
        }
        

        【讨论】:

          【解决方案4】:

          当前version of AutofacPropertiesAutowired 定义了可选的IPropertySelector 参数,用于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();
          

          【讨论】:

          • 我只是尝试安装 Autofac.Core.NonPublicProperty,它带来了大量的依赖项,在我看来完全没有动力。诸如密码学等之类的东西在运行时也不起作用。不推荐。
          • @jool 你确定吗?它只带来Autofac (4.5.0) 仅此而已。 check image
          • @tchelidze 我遇到了同样的问题。我的项目以 .NET Framework (net472) 为目标,因为您的代码以 .NET Standard 为目标,所以安装了各种 System.* 包。有没有办法避免这种情况?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-02
          • 1970-01-01
          相关资源
          最近更新 更多