2009-09-04 16 views
10

Sto utilizzando il contenitore Unity IoC e devo intercettare tutte le chiamate a Risolvi per una determinata interfaccia di base ed eseguire il mio codice personalizzato per costruirle.Estensione di fabbrica dell'oggetto personalizzato per Unity

In altre parole, nel codice di esempio riportato di seguito, quando chiamo container.Resolve<IFooN>(), se non ha un'istanza del tipo concreto di attuazione, si chiama MyFactoryFunction di costruire uno, altrimenti lo voglio restituire la copia cache.

Il contenitore Unity standard non è in grado di costruire questi oggetti (aggiornamento : perché sono oggetti remoti .NET, quindi le classi concrete non esistono in nessun assembly nel computer locale) e non desidero per crearli in anticipo e memorizzarli con RegisterInstance.

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

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

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

Sto assumendo che posso creare un'estensione Unità per fare questo, ma mi chiedevo se esiste già una soluzione là fuori che posso prendere in prestito.

risposta

8

Per completezza, devo aggiungere un'altra risposta che lavora sotto l'Unità 2, dal momento che la mia altra risposta non funziona più. È leggermente più coinvolto dal momento che è necessario creare un criterio personalizzato per i builder. Grazie alla ctavares del progetto Unity che ha fornito un sacco di aiuto su this thread nell'attuazione della presente:

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

Il contenitore Unity agisce già come una factory che sa come costruire (ed eseguire l'injection dependency per) tipi arbitrari, quindi la factory che accetta un Type t appare ridondante. Puoi approfondire il motivo per cui non è possibile?

Se questo non è veramente possibile (più probabilmente solo troppo lavoro), allora forse è possibile registrare la fabbrica con il contenitore?

+0

la ragione è che stiamo utilizzando .NET remoting per creare la classe. Quindi non possiamo semplicemente chiamare il suo costruttore - il tipo concreto non esiste in nessun assembly sul client.Dobbiamo connetterci a un server e richiedere il suo oggetto che soddisfi l'interfaccia in questione. –

5

Aggiornamento Questa risposta era per Unity 1.2. Per una soluzione che funziona con Unity 2, vedi la mia altra risposta.


OK, ho implementato l'estensione me stesso. Nel builder metto in cache l'oggetto come voglio che sia un singleton w.r.t il mio contenitore. Il motivo per baseContext è che voglio che venga memorizzato nella cache nel contenitore di livello superiore anziché in qualsiasi contenitore secondario da cui è stato richiesto.

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

Aggiunta l'estensione è molto semplice:

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

potresti spiegare la tua strategia di cache? Perché è necessario aggiungere un tipo creato al contenitore dopo la creazione? –

+0

@Dmitriy - perché voglio solo creare ciascuna interfaccia remota .NET una volta –

5

Unità (v2) consente di specificare una fabbrica. Permette un paio di diverse funzioni, tra cui prendere il tipo di costruire/nome, ecc Semplice esempio:

UnityContainer cont = new UnityContainer(); 

avere un semplice metodo create di prova - ma questo può essere espansa con qualsiasi fabbrica che si desidera.
questo eluderà il processo di creazione normale che è (per brevità) chiamare il costruttore più lungo tutti i comportamenti successivi, compresa l'impostazione di propert, verranno comunque eseguiti.

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

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

Sembra che devi usare la forma generica di RegisterType brendanjerwin

Problemi correlati