2009-09-04 6 views
10

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

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

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

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

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

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

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

ответ

8

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

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's already created 
      context.BuildComplete = true; 
     } 
    } 
} 
-1

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

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

+0

Причина в том, что мы используем удаленный доступ .NET для создания класса. Поэтому мы не можем просто вызвать его конструктор - конкретный тип не существует в какой-либо сборке на клиенте.Мы должны подключиться к серверу и запросить его объект, который выполняет соответствующий интерфейс. –

5

Обновление Этот ответ был дан для 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)); 
+0

Не могли бы вы объяснить свою стратегию кэша? Зачем нужно добавлять созданный тип в контейнер после создания? –

+0

@Dmitriy - потому что я хочу только создать каждый удаленный интерфейс .NET –

5

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

UnityContainer cont = new UnityContainer(); 

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

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

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

Похоже, вам нужно использовать общую форму RegisterType brendanjerwin

Смежные вопросы