Как внедрить два экземпляра одного и того же объекта с помощью Autofac? - PullRequest
4 голосов
/ 24 марта 2011

Я использую инъекцию конструктора Autofac. Мне нужно выяснить, как внедрить один экземпляр объекта в несколько аргументов конструктора, без необходимости явно разрешать каждый аргумент на этапе настройки контейнера.

У меня сложный сценарий, который будет упрощен этим поведением; следующий пример - просто упрощенный сценарий, поэтому я могу продемонстрировать искомое поведение.

Пример:

Допустим, у меня есть два интерфейса: IOpenable и ICloseable:

public interface IOpenable
{
    void Open();
}
public interface ICloseable
{
    void Close();
}

И у меня есть класс Door, который реализует их оба:

public interface IDoor : IOpenable, ICloseable { }
public class Door : IDoor, IOpenable, ICloseable 
{
    void Open() { ... }
    void Close() { ... }
}

И у меня есть этот класс, который принимает IOpenable и ICloseable:

public class DoorHandler : IDoorHandler
{
    public DoorHandler(IOpenable openable, ICloseable closeable)
    {
        ...
    }
    ...
}

Вопрос:

Можно ли сказать autofac, что нужно вводить один и тот же Door объект в оба аргумента, когда оба IOpenable и ICloseable являются зависимостями в одном конструкторе?

Примечание: я не могу сделать :

container.Register<IDoorHandler>( c => {
    Door d = c.Resolve<IDoor>();
    return new DoorHandler(d,d)
});

То, что будет делать то, что я хочу, но помните, что этот класс DoorHandler является лишь примером. В моем реальном коде «DoorHandler» действительно является контроллером MVC, и я регистрирую его с помощью RegisterControllers (). Поэтому я не могу зарегистрировать это, как указано выше. Кроме того, иногда графы зависимостей могут быть чрезмерно сложными, и выполнение этого явно в каждом случае может стать чрезмерным.

Полагаю, мне нужно сделать что-то вроде:

container.RegisterType<DoorHandler>().As<IDoorHandler>();
container.RegisterType<Door>().As<IDoor>();
container.Register<IOpenable>( c => c.ResolveShared<IDoor>(); );
container.Register<ICloseable>( c => c.ResolveShared<IDoor>(); );

где вызовы c.ResolveShared<T> преобразуются в один и тот же объект T, если вызывается для более чем одного аргумента в одном конструкторе.

Или, возможно:

container.RegisterType<DoorHandler>().As<IDoorHandler>();
container.RegisterType<Door>().As<IDoor>().InstancePerDependencyShared();
container.Register<IOpenable>( c => c.Resolve<IDoor>(); );
container.Register<ICloseable>( c => c.Resolve<IDoor>(); );

Очевидно, что если бы я использовал InstancePerLifetimeScope или что-то для объекта Door, каждая разрешенная Door была бы одним и тем же экземпляром. Но я не хочу этого, я хочу новый экземпляр Door каждый раз, когда создается DoorHandler, и я хочу, чтобы эта Door передавалась как оба аргумента конструктору DoorHandler.

Ответы [ 2 ]

3 голосов
/ 25 марта 2011

Ok tricky one :) ... Вот одно из возможных решений для общего совместного использования «на одного конструктора»:

builder.RegisterControllers(asm)        
    .OnPreparing(e => {
        var spr = new SharedConstructorParameter(
            typeof(IOpenable),
            typeof(ICloseable));
        e.Parameters = new Parameter[]{ spr }.Concat(e.Parameters);
    });

Параметр необходимо настроить в событии OnPreparing(), поскольку SharedConstructorParameterэкземпляр будет кэшем значений для каждой операции разрешения.

class SharedConstructorParameter : Parameter
{
    object _cachedInstance;
    Type[] _sharedParameterTypes;

    public SharedConstructorParameter(params Type[] sharedParameterTypes)
    {
        _sharedParameterTypes = sharedParameterTypes;
    }

    protected override bool CanSupplyValue(
        ParameterInfo pi,
        IComponentContext cc,
        out Func<object> valueCreator)
    {
        valueCreator = null;
        if (!_sharedParameterTypes.Contains(pi.ParameterType))
            return false;

         valueCreator = () => {
             _cachedInstance = _cachedInstance ?? cc.Resolve(pi.ParameterType);
             return cachedInstance;
         };
         return true;
    }
}

Ваша возможность компилировать и отлаживать;)

2 голосов
/ 25 марта 2011

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

При интеграции ASP.NET в Autofac каждый входящий HTTP-запрос имеет свою собственную область действия, поэтому если у вас есть это ...

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly);
// Under the covers, this is really doing...
// builder.RegisterType<DoorController>().InstancePerHttpRequest();

Это InstancePerHttpRequest является расширением, похожим на InstancePerLifetimeScope.Новая область действия времени жизни создается вокруг вашего HTTP-запроса и располагается в конце.

Затем вы можете зарегистрировать ваши общие объекты времени жизни также как InstancePerHttpRequest:

builder.RegisterType<Door>().As<IDoor>().InstancePerHttpRequest();
builder.RegisterType<Openable>().As<IOpenable>();
builder.RegisterType<Closeable>().As<ICloseable>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

А теперь, когда ваш контроллерразрешено, IDoor будет одинаковым экземпляром как в экземплярах IOpenable, так и в ICloseable.

Если вы находитесь за пределами области запроса, лучшее, что вы можете сделать, это что-то вроде:

var builder = new ContainerBuilder();
builder.RegisterType<DoorHandler>().As<IDoorHandler>();
builder.RegisterType<Door>().As<IDoor>().InstancePerLifetimeScope();
builder.RegisterType<Openable>().As<IOpenable>();
builder.RegisterType<Closeable>().As<ICloseable>();
var container = builder.Build();

Зарегистрируйте «общие» элементы как InstancePerLifetimeScope.Затем, когда вам нужно решить, вы можете сделать что-то вроде:

using(var lifetime = container.BeginLifetimeScope())
{
  var dh = lifetime.Resolve<IDoorHandler>();
  // The IDoor will be the same in both references here.
}

Технически вы можете поместить ссылку на обработчик двери вне оператора using, но тогда, если ваши реализации IDoor будут одноразовыми, они получатУтилизировать вместе со сроком службы в конце использования, поэтому будьте осторожны с этим.

...