2015-02-09 23 views
5

Ho un interfaccia, implementazione e di destinazione:Come creare una dipendenza opzionale in AutoFac?

public interface IPerson { public string Name { get; } } 
public class Person: IPerson { public string Name { get { return "John"; } } } 
public class Target { public Target(IPerson person) {} } 

sto usando Autofac per legare insieme le cose:

builder.RegisterType<Person>().As<IPerson>().SingleInstance(); 

Il problema è che IPerson vive in un assembly condiviso, Person vive in un plugin (che può o non può esserci), e Target vive nell'applicazione principale che carica i plugin. Se non ci sono plugin caricati che implementano IPerson, Autofac diventa irritato per non essere in grado di risolvere le dipendenze di Target. E non posso davvero biasimarlo per quello.

Tuttavia so che Target è in grado di gestire la mancanza di un IPerson e sarebbe più che felice di ottenere un null invece. In effetti, sono abbastanza sicuro che tutti i componenti che si basano su un IPerson sono pronti a prendere il numero null. Quindi, come posso dire ad Autofac: "Ok, dolcezza, non ti preoccupare, dammi solo un null, va bene?"

Un modo che ho trovato è quello di aggiungere un parametro predefinito per Target:

public class Target { public Target(IPerson person = null) {} } 

che funziona, ma poi ho bisogno di fare questo per tutti i componenti che richiedono un IPerson. Posso fare anche il contrario? In qualche modo dire ad Autofac "Se tutto il resto fallisce per risolvere IPerson, restituire null"?

+0

Vedere anche 'ResolveOptional()'. – abatishchev

+0

Se 'Person' vive nel plugin, come puoi registrarlo? Dove va questa chiamata? 'builder.RegisterType () .As () .SingleInstance();' –

+1

@SriramSakthivel - ogni plugin ha un metodo 'Initialize (ContainerBuilder)' chiamato dall'applicazione principale. I plugin registrano i loro componenti lì. –

risposta

3

È possibile utilizzare questa sintassi:

builder.RegisterType<Target>().WithParameter(TypedParameter.From<IPerson>(null)); 

Purtroppo

builder.Register(c => (IPerson)null).As<IPerson>(); 
    // will throw : Autofac.Core.DependencyResolutionException: A delegate registered to create instances of 'ConsoleApplication17.Program+IPerson' returned null. 

e

builder.RegisterInstance<IPerson>(null).As<IPerson>(); 
    // will throw : Unhandled Exception: System.ArgumentNullException: Value cannot be null. 

Se non si desidera aggiungere un WithParameter per ogni registrazione, è possibile aggiungere un modulo che lo farà per te

public class OptionalAutowiringModule : Autofac.Module 
{ 
    public OptionalAutowiringModule(IEnumerable<Type> optionalTypes) 
    { 
     this._optionalTypes = optionalTypes; 
    } 
    public OptionalAutowiringModule(params Type[] optionalTypes) 
    { 
     this._optionalTypes = optionalTypes; 
    } 


    private readonly IEnumerable<Type> _optionalTypes; 


    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) 
    { 
     base.AttachToComponentRegistration(componentRegistry, registration); 

     registration.Preparing += (sender, e) => 
     { 
      e.Parameters = e.Parameters.Concat(new Parameter[] { new OptionalAutowiringParameter(this._optionalTypes) }); 
     }; 
    } 
} 
public class OptionalAutowiringParameter : Parameter 
{ 
    public OptionalAutowiringParameter(IEnumerable<Type> optionalTypes) 
    { 
     this._optionalTypes = optionalTypes.ToList(); 
    } 


    private readonly List<Type> _optionalTypes; 


    public override Boolean CanSupplyValue(ParameterInfo pi, IComponentContext context, out Func<Object> valueProvider) 
    { 
     if (this._optionalTypes.Contains(pi.ParameterType) && !context.IsRegistered(pi.ParameterType)) 
     { 
      valueProvider =() => null; 
      return true; 
     } 
     else 
     { 
      valueProvider = null; 
      return false; 
     } 
    } 
} 

Poi, tutto quello che dovete fare è registrare il modulo con i tuoi dipendenze opzionali

builder.RegisterModule(new OptionalAutowiringModule(typeof(IPerson))); 

Ma invece di iniettare un riferimento null che può causare un NullReferenceException. Un'altra soluzione potrebbe essere quella di creare l'implementazione NullPerson.

builder.RegisterType<NullPerson>().As<IPerson>(); 
    builder.RegisterType<Target>(); 

Quando hai il tuo reale implementazione registrato solo ancora una volta, sarà sovrascrivere l'implementazione originale.

+0

Bene, la prima opzione non è molto meglio che semplicemente specificare '= null' nella dichiarazione dei parametri. In realtà, è ancora più lungo. Un 'NullPerson' è una specie di soluzione, ma piuttosto scomoda. –

+0

Ho aggiornato il mio esempio per evitare la registrazione di un parametro per ogni registrazione utilizzando un modulo –

+0

OK, quindi un'origine di registrazione personalizzata. Bene, immagino che sia il miglior modo possibile allora. Grazie! :) –

0

Come soluzione temporanea, è possibile iniettare una facciata, simile a Lazy<IPerson>, che tenterà di risolverlo dal contenitore quando viene chiamato.

class PersonFacade 
{ 
    public PersonFacade(Func<IPerson> func) 
    { 
     _func = func; 
    } 

    public IPerson Value 
    { 
     // will Autofac throw exception if plugin is missing? 
     try 
     { 
      return _func(); 
     } 
     catch 
     { 
      return null; 
     } 
    } 
} 

// register me 
new PersonFacade(() => container.Resolve<IPerson>()); 
0

Il normale meccanismo per dichiarare dipendenze opzionali è di inserirli in proprietà anziché argomenti del costruttore.

3

Basta usare parametri opzionali, vedere il seguente esempio:

public class SomeClass 
{ 
    public SomeClass(ISomeDependency someDependency = null) 
    { 
      // someDependency will be null in case you've not registered that before, and will be filled whenever you register that. 
    } 
} 
1

È potrebbe basta prendere la tua dipendenza da IPerson person = null nel costruttore, che è una dichiarazione implicita di un IPerson dipendenza opzionale (cf @YaserMoradi) . Tuttavia, che si mette nella posizione di dover consolidare questo, ora e per sempre dopo:

" ... Sono abbastanza sicuro che tutti i componenti che si basano su un IPerson sono disposti a prendere un null esso al suo posto. "

Meglio che questo non debba essere una domanda.

Il "best practice" modello (che @CyrilDurand dà come suffisso per la sua risposta) per questo è di utilizzare un default implementation (risultato link: Autofac utilizzerà l'ultimo componente registrato come provider predefinito di tale servizio). Se non si dispone di altre implementazioni provenienti dal proprio plug-in (registrato dopo l'impostazione predefinita) verrà utilizzata questa impostazione predefinita.

Nel tuo caso, il componente predefinito dovrebbe essere una sorta di implementazione no-op o base del servizio IPerson, in cui qualsiasi metodo chiamato avrà qualsiasi cosa costituisca il comportamento predefinito per l'applicazione. Ciò fornisce anche una migliore storia di riutilizzo, dal momento che è possibile definire il comportamento predefinito una volta per tutte.