E 'possibile e/o una buona idea di utilizzare Ninject (o qualsiasi altro contenitore CIO, è per questo) per creare un legamedi default per situazioni in cui non esiste un'implementazione appropriata e utilizzare questo bind predefinito piuttosto che gestire uno ActivationException
quando esistono più associazioni o non esistono associazioni per una particolare richiesta?Utilizzando Ninject e vincolante di un'implementazione di default, evitando la temuta Service Locator anti-modello
Sono stato utilizzando Factory e Conventions progetti di ampliamento di Ninject, ma mi chiedo se stanno mascherando un errore che sto facendo a un livello più fondamentale, così ho creato un test per illustrare quello che voglio di fare, nel modo più semplice che posso:
Dato il seguente:
public interface IWidget { }
public class DefaultWidget : IWidget { }
public class BlueWidget : IWidget { }
E il xUnit prova seguente utilizzando FluentAssertions:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// intention: resolve a `DefaultWidget` implementation whenever the
// 'name' parameter does not match the name of any other bound implementation
kernel.Bind<IWidget>().To<DefaultWidget>();
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
}
Non so esattamente come percorrere questa strada, se è possibile, mi porterebbe a una sgradevole implementazione del modello di Localizzatore di servizi, a parte il fatto che sembra essere un avvertimento dato da coloro che hanno risposto a domande simili.
Quindi, è un abuso/uso improprio di contenitori IoC per fare ciò che sto chiedendo di fare?
Sembra che dovrei voler associare/risolvere tipi con un IoC per tutti i tipi espliciti che ho, quindi quella parte non mi sembra sbagliata. Nella vita reale, ci sarebbero attacchi molti di più espliciti come BlueWidget
, così come un numero imprecisato di variazioni sul valore stringa "RedWidget" in arrivo.
In generale, sembra che l'idea di un'implementazione predefinita di alcune interfacce è una situazione non rara, quindi dove sarebbe questo meccanismo per la risoluzione delle richieste, se non all'interno del regno del contenitore IoC?
Ho anche intenzione di utilizzare il modello di fabbrica per creare le implementazioni IWidget
. Finora, ho utilizzato le fabbriche create automaticamente da Ninject.Extensions.Factory con provider di istanze personalizzati e generatori di binding personalizzati, ma non posso superare questo problema.
Avrebbe maggiore controllo sull'implementazione di fabbrica (in altre parole, utilizzare la mia fabbrica, non la fabbrica automatica da Ninject.Extensions.Factory)? In passato, ho utilizzato Assembly reflection per trovare i tipi di candidati e utilizzare Activation.CreateInstance() per creare l'implementazione specifica di cui ho bisogno o un'implementazione predefinita, ma questo diventa davvero ingombrante una volta che queste implementazioni hanno le proprie dipendenze con l'aggiunta del costruttore come principi di iniezione di dipendenza vengono applicati a tali implementazioni. Quindi, il mio passaggio ai contenitori IoC per una soluzione - ma questo non sta funzionando come avrei sperato.
UPDATE 1 - successo con mia propria fabbrica ATTUAZIONE
io non sono davvero contento di questo, perché ogni volta che una nuova implementazione di IWidget
deve essere scritto, dovrò crack aprire questa fabbrica e aggiornalo anche tu. Nel mio esempio qui, dovrei aggiungere anche un'altra riga nei binding, ma è qui che entra in gioco l'associazione basata sulla convenzione, che avrei pianificato di utilizzare per evitare di dover aggiornare costantemente le definizioni di binding.
Utilizzando questa implementazione fabbrica,
public interface IWidgetFactory { IWidget Create(string name); }
public class WidgetFactory : IWidgetFactory
{
private readonly IKernel kernel;
public WidgetFactory(IKernel kernel) { this.kernel = kernel; }
public IWidget Create(string name)
{
switch (name)
{
case "Blue":
return this.kernel.Get<IWidget>(typeof (BlueWidget).Name);
default:
return this.kernel.Get<IWidget>(typeof (DefaultWidget).Name);
}
}
}
posso ottenere questa prova da superare:
[Fact]
public void WidgetBuilders_And_Customizers_And_Bindings_Oh_My()
{
StandardKernel kernel = new StandardKernel();
kernel.Bind<IWidget>().To<DefaultWidget>().Named(typeof(DefaultWidget).Name);
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
kernel.Bind<IWidgetFactory>().To<WidgetFactory>().InSingletonScope();
kernel.Get<IWidgetFactory>().Create("Blue")
.Should().BeOfType<BlueWidget>();
kernel.Get<IWidgetFactory>().Create("Red")
.Should().BeOfType<DefaultWidget>();
}
Funziona, ma non si sente bene, per i seguenti motivi:
- Devo iniettare lo
IKernel
nelloIWidgetFactory
- Per ogni nuova implementazione del
IWidget
, ilIWidgetFactory
deve essere aggiornato - Sembra un'estensione Ninject dovrebbe essere stato creato per questo scenario già
FINE UPDATE 1
Cosa sarebbe si fa in questa situazione, considerando che il conteggio delle implementazioni IWidget
è alto, l'intervallo previsto di argomenti "nome widget" è essenzialmente infinito e tutti i nomi di widget non risolvibili devono essere gestiti w con il DefaultWidget
?
Non c'è bisogno di leggere oltre, ma se siete interessati, ecco le varie prove ho provato come ho lavorato su questo problema:
Ecco l'evoluzione completa di test che ho passato:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// evolution #1: simple as possible
// PASSES (as you would expect)
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #2: make the only binding to a different IWidget
// FAILS (as you would expect)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #3: add the binding to `BlueWidget` back
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #4: make `BlueWidget` binding a *named binding*
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #5: change `Get<>` request to specifiy widget name
// PASSES (yee-haw!)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #6: make `BlueWidget` binding *non-named*
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #7: ask for non-existance `RedWidget`, hope for `DefaultWidget`
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #8: make `BlueWidget` binding a *named binding* again
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #9: remove `RedWidget` specification in Get<> request
// ACTIVATION EXCEPTION (back to **more than one** binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<DefaultWidget>();
}
Hai chiesto una grande domanda e hanno chiaramente messo in un sacco di lavoro e io Sono molto felice di +1. Personalmente sono confuso dal risultato di # 8 e verificato manualmente! Il prossimo passo è andare a dare un'occhiata ai test per Named Bindings e a) cercare di capirli per migliorare la tua domanda b) provare a capirlo in modo da poter implementare la tua soluzione usando i tuoi metadati di binding (credimi sarà facile) ma idealmente scoprirai dai test se tutto ciò che stai osservando ha senso, quindi invia un bug report o una Pull Request o apporta una modifica alla tua domanda –
@Ruben, grazie per il commento - questi test sono repository principale di GitHub di Ninject? Non riesco a trovare alcun file di test come "NamedBindingsTests", sebbene abbia localizzato BindingMetadata nella cartella "Planning". – Jeff
Dunno - stavo solo ipotizzando in base alle mie precedenti esplorazioni, 2s ... sembra che https://github.com/ninject/ninject/blob/master/src/Ninject.Test/Integration/StandardKernelTests.cs sia il più vicino. Non riesco a ricordare se c'è una wiki, un post sul blog o una risposta SO ben trafficata sul comportamento di denominazione predefinito come lo stai chiedendo, ma sono abbastanza sicuro che sia da qualche parte (personalmente personalmente ne faccio il nome o nessuno, quindi non è mai sorto come preoccupazione) –