Пользовательский объект factory для Unity

Я использую контейнер Unity IoC, и мне нужно перехватить любые вызовы Resolve для определенного базового интерфейса и запустить собственный код для создания этих типов.

Другими словами, в примере кода ниже, когда я вызываю container.Resolve<IFooN>(), если у него нет экземпляра конкретного типа реализации, он вызывает MyFactoryFunction для его создания, иначе я хочу, чтобы он возвращал кешированная копия.

Стандартный контейнер Unity не способен создавать эти объекты ( update:, поскольку они являются объектами удаленного доступа .NET, поэтому конкретные классы не существуют ни в одной сборке на локальном компьютере), и я не хотите создавать их спереди и хранить их с помощью RegisterInstance.

interface IFoo : IBase { ... }
interface IFoo2 : IBase { ... }

...
container.Resolve<IFoo2>();

...
IBase MyFactoryFunction(Type t)
{
    ...
}

Я предполагаю, что могу создать расширение Unity для этого, но мне было интересно, есть ли там решение, которое я могу взять.

Ответ 1

Для полноты я должен добавить еще один ответ, который работает под Unity 2, так как мой другой ответ больше не работает. Это немного больше связано с тем, что вам нужно создать собственную политику строителя. Благодаря ctavares из проекта Unity, который предоставил большую помощь этой теме в реализации этого:

public class FactoryUnityExtension : UnityContainerExtension
{
    private ICustomFactory factory;
    private CustomFactoryBuildStrategy strategy;

    public FactoryUnityExtension(ICustomFactory factory)
    {
        this.factory = factory;
    }

    protected override void Initialize()
    {
        this.strategy = new CustomFactoryBuildStrategy(factory, Context);
        Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
        Context.Policies.Set<ParentMarkerPolicy>(new ParentMarkerPolicy(Context.Lifetime), new NamedTypeBuildKey<ParentMarkerPolicy>());
    }
}

public class ParentMarkerPolicy : IBuilderPolicy
{
    private ILifetimeContainer lifetime;

    public ParentMarkerPolicy(ILifetimeContainer lifetime)
    {
        this.lifetime = lifetime;
    }

    public void AddToLifetime(object o)
    {
        lifetime.Add(o);
    }
}

public interface ICustomFactory
{
    object Create(Type t);
    bool CanCreate(Type t);
}

public class CustomFactoryBuildStrategy : BuilderStrategy
{
    private ExtensionContext baseContext;
    private ICustomFactory factory;


    public CustomFactoryBuildStrategy(ICustomFactory factory, ExtensionContext baseContext)
    {
        this.factory = factory;
        this.baseContext = baseContext;
    }

    public override void PreBuildUp(IBuilderContext context)
    {
        var key = (NamedTypeBuildKey)context.OriginalBuildKey;

        if (factory.CanCreate(key.Type) && context.Existing == null)
        {
            context.Existing = factory.Create(key.Type);
            var ltm = new ContainerControlledLifetimeManager();
            ltm.SetValue(context.Existing);

            // Find the container to add this to
            IPolicyList parentPolicies;
            var parentMarker = context.Policies.Get<ParentMarkerPolicy>(new NamedTypeBuildKey<ParentMarkerPolicy>(), out parentPolicies);

            // TODO: add error check - if policy is missing, extension is misconfigured

            // Add lifetime manager to container
            parentPolicies.Set<ILifetimePolicy>(ltm, new NamedTypeBuildKey(key.Type));
            // And add to LifetimeContainer so it gets disposed
            parentMarker.AddToLifetime(ltm);

            // Short circuit the rest of the chain, object already created
            context.BuildComplete = true;
        }
    }
}

Ответ 2

Обновление. Этот ответ был для Unity 1.2. Для решения, которое работает с Unity 2, см. Мой другой ответ.


ОК, я сам реализовал расширение. В построителе я кэширую объект, так как я хочу, чтобы он был единственным ядром w.r.t моего контейнера. Причина baseContext заключается в том, что я хочу, чтобы он был кэширован в контейнере верхнего уровня, а не в каких-либо дочерних контейнерах, из которых он был запрошен.

 public class FactoryMethodUnityExtension<T> : UnityContainerExtension
 {
     private Func<Type,T> factory; 

     public FactoryMethodUnityExtension(Func<Type,T> factory)
     {
         this.factory = factory;
     }

     protected override void Initialize()
     {
         var strategy = new CustomFactoryBuildStrategy<T>(factory, this.Context);
         Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);             
     }
 } 

 public class CustomFactoryBuildStrategy<T> : BuilderStrategy
 {
     private Func<Type,T> factory;
     private ExtensionContext baseContext;

     public CustomFactoryBuildStrategy(Func<Type,T> factory, ExtensionContext baseContext)
     {
        this.factory = factory;
        this.baseContext = baseContext;
     }

     public override void PreBuildUp(IBuilderContext context)
     {
         var key = (NamedTypeBuildKey)context.OriginalBuildKey;

         if (key.Type.IsInterface && typeof(T).IsAssignableFrom(key.Type))
         {
             object existing = baseContext.Locator.Get(key.Type);
             if (existing == null)
             {
                 // create it
                 context.Existing = factory(key.Type);
                 // cache it
                 baseContext.Locator.Add(key.Type, context.Existing);
             }
             else
             {
                 context.Existing = existing;
             }
         }
     }
 }

Добавление расширения довольно просто:

MyFactory factory = new MyFactory();
container = new UnityContainer();
container.AddExtension(new FactoryMethodUnityExtension<IBase>(factory.Create));

Ответ 3

Unity (v2) позволяет указать factory. Он позволяет использовать несколько различных функций, включая создание типа для создания/имени и т.д. Простой пример:

UnityContainer cont = new UnityContainer();

имеют простой метод создания теста - но это можно расширить с помощью любого factory, который вы хотите.
это будет обходить нормальный процесс создания, который (ради краткости) вызывает самый длинный конструктор все последующие действия, включая настройку правильности, будут выполняться.

cont.RegisterType<TestClass>(new InjectionFactory(c => CreateTest()));

var svc = cont.Resolve<TestClass>();

Ответ 4

Контейнер Unity уже действует как factory, который знает, как построить (и выполнить инъекцию зависимостей для) произвольных типов, поэтому ваш factory, который принимает тип t, выглядит излишним. Можете ли вы подробнее рассказать о том, почему это невозможно?

Если это действительно невозможно (скорее всего, слишком много работы), то, возможно, вы можете зарегистрировать свой factory с контейнером вместо этого?