Внедрение свойства с помощью внутреннего сеттера

У меня есть существующее приложение, которое я модифицирую для использования Autofac Property Injection. Кажется, независимо от того, какой метод я использую для регистрации своих типов со свойствами, свойства всегда равны нулю, если только они не имеют общедоступных сеттеров. С другими контейнерами IoC (например, Structuremap) можно ограничить внутренний установщик и сделать его доступным с помощью атрибута InternalsVisibleTo в сборке. Было бы неплохо запретить клиентам изменять назначение.

Возможно ли это с Autofac? Или есть другой подход при работе с внедрением свойств для обеспечения безопасности назначений?

Я пытался использовать отражение с PropertiesAutoWired(), а также разрешить .WithParameter() из моего WebApi Global.asax, указав конкретный параметр, который нужно установить, но безуспешно в качестве внутреннего установщика.

[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
            }
        }
    }
}

person Fratt    schedule 12.08.2013    source источник


Ответы (4)


Вы не можете внедрить сеттеры internal с помощью autofac, потому что класс AutowiringPropertyInjector ищет только общедоступные свойства (см. источник).

Однако логика в 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));

Однако вышеперечисленное решение теперь вводит все виды свойств, даже частные и защищенные, поэтому вам может потребоваться расширить его некоторыми дополнительными проверками, чтобы убедиться, что вы вводите только те свойства, которые ожидаете.

person nemesv    schedule 12.08.2013

Я использую такое решение:

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.

person oddbear    schedule 24.07.2015

Также мы можем написать реализацию @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();
}
person Oğuzhan Soykan    schedule 11.10.2016

Текущая версия Autofac определяет необязательный параметр IPropertySelector для PropertiesAutowired, который используется для отфильтровать внедряемые свойства.

реализация по умолчанию для 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();
person tchelidze    schedule 22.09.2017
comment
Я только что попытался установить Autofac.Core.NonPublicProperty, он приносит безумное количество зависимостей, которые мне кажутся совершенно немотивированными. Такие вещи, как криптография и т. Д. Также не работают во время работы. Не рекомендуется. - person jool; 08.12.2017
comment
@jool ты уверен? это только Autofac (4.5.0) больше ничего не приносит. проверить изображение - person tchelidze; 30.12.2017
comment
@tchelidze У меня такая же проблема. Мой проект нацелен на .NET Framework (net472), а поскольку ваш код нацелен на .NET Standard, устанавливаются все виды пакетов System.*. Есть ли способ избежать этого? - person TravelingFox; 17.12.2019