2009-08-18 10 views
17

Sono un principiante quando si tratta di DI e ninject e sto facendo fatica un po 'su su quando dovrebbe avvenire l'iniezione vera e come iniziare il binding .Ninject - come e quando iniettare

Lo sto usando già nella mia applicazione web e funziona perfettamente lì, ma ora voglio usare l'iniezione in una libreria di classi.

Dire che ho una classe come questa:

public class TestClass 
{ 
    [Inject] 
    public IRoleRepository RoleRepository { get; set; } 
    [Inject] 
    public ISiteRepository SiteRepository { get; set; } 
    [Inject] 
    public IUserRepository UserRepository { get; set; } 

    private readonly string _fileName; 

    public TestClass(string fileName) 
    { 
     _fileName = fileName; 
    } 

    public void ImportData() 
    { 
     var user = UserRepository.GetByUserName("myname"); 
     var role = RoleRepository.GetByRoleName("myname"); 
     var site = SiteRepository.GetByID(15); 
     // Use file etc 
    } 

} 

voglio utilizzare l'iniezione di proprietà qui perché ho bisogno di passare in un nome di file nel mio costruttore. Ho ragione nel dire che se ho bisogno di passare in un parametro costruttore, non posso usare l'iniezione del costruttore? Se posso utilizzare l'iniezione del costruttore con parametri aggiuntivi, come faccio a passare questi parametri in ?

Ho una console app che consuma per classe di test che sembra come segue:

class Program 
{ 
    static void Main(string[] args) 
    { 
     // NinjectRepositoryModule Binds my IRoleRepository etc to concrete 
     // types and works fine as I'm using it in my web app without any 
     // problems 
     IKernel kernel = new StandardKernel(new NinjectRepositoryModule()); 

     var test = new TestClass("filename"); 

     test.ImportData(); 
    } 
} 

mio problema è che quando chiamo test.ImportData() mio repository sono nulli - nulla è stato iniettato in loro. Ho provato a generare un altro modulo e chiamando

Bind<TestClass>().ToSelf(); 

come ho pensato che questo potrebbe risolvere tutte le proprietà di iniezione in TestClass ma sto andando da nessuna parte.

Sono sicuro che questo è un problema banale, ma proprio non riesco a scoprire come fare per questo.

risposta

18

ci si trova direttamente Newing TestClass, che Ninject non ha modo di intercettare - ricordate non c'è nessuna magia come il codice di trasformazione intercettare il vostro new s ecc

si dovrebbe fare invece kernel.Get<TestClass>.

In mancanza di questo, si può iniettare dopo sinew con un kernel.Inject(test);

Penso che ci sia un articolo del wiki che parla di Inject vs Get ecc

Si noti che, in generale, diretta Get o Inject le chiamate sono un cattivo odore di posizione di servizio, che è un antipattern. Nel caso della tua web app, lo NinjectHttpModule e lo PageBase sono gli hook che intercettano la creazione dell'oggetto - ci sono intercettori/luoghi logici simili da intercettare in altri stili di app.

Re vostra Bind<TestClass>().ToSelf(), in genere un StandardKernel ha ImplicitSelfBinding = true che renderebbe inutile che (a meno che non si vuole influenzare la sua portata a essere qualcosa di diverso .InTransientScope()).

Un punto di stile finale: - stai usando l'iniezione di proprietà. Ci sono raramente buoni motivi per questo, quindi dovresti usare invece l'iniezione del costruttore.

E vai a comprare Dependency Injection in .NET by @Mark Seemann, che ha pile di post eccellenti qui intorno che coprono molte considerazioni importanti ma sottili dentro e intorno all'area dell'iniezione delle dipendenze.

+0

L'utilizzo di kernel.Inject (test) sembra funzionare. Grazie. Per curiosità un'altra domanda - Sto testando nuovamente TestClass perché ho bisogno di passare una stringa nel costruttore. C'è un modo per passare un parametro usando kernel.Get ()? –

+0

Sì - ci sono sovraccarichi di Get e puoi anche inserirli nel Legatore –

+0

Generalmente un patttern migliore al posto del cablaggio è che la cosa che ha bisogno del constructor arg invece fa riferimento a un oggetto confuigurator che hai di nuovo up e quindi Bind durante la creazione del modulo –

7

OK,

ho trovato il modo di fare quello che mi serve, in parte grazie ai vostri commenti Ruben. Ho creato un nuovo modulo che contiene fondamentalmente la configurazione che utilizzo nella libreria di classi. All'interno di questo modulo posso legare usando un'interfaccia segnaposto o posso aggiungere un parametro costruttore al CustomerLoader. Di seguito è riportato il codice di un'app console fittizia per dimostrare entrambi i modi.

Questo potrebbe aiutare qualcun altro a iniziare con Ninject!

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Ninject.Core; 
using Ninject.Core.Behavior; 

namespace NinjectTest 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      var kernel = new StandardKernel(new RepositoryModule(), new ProgramModule());    
      var loader = kernel.Get<CustomerLoader>(); 
      loader.LoadCustomer(); 
      Console.ReadKey(); 
     } 
    } 

    public class ProgramModule : StandardModule 
    { 
     public override void Load() 
     { 
      // To get ninject to add the constructor parameter uncomment the line below 
      //Bind<CustomerLoader>().ToSelf().WithArgument("fileName", "string argument file name"); 
      Bind<LiveFileName>().To<LiveFileName>(); 
     } 
    } 

    public class RepositoryModule : StandardModule 
    { 
     public override void Load() 
     { 
      Bind<ICustomerRepository>().To<CustomerRepository>().Using<SingletonBehavior>(); 
     } 
    } 

    public interface IFileNameContainer 
    { 
     string FileName { get; } 
    } 
    public class LiveFileName : IFileNameContainer 
    { 
     public string FileName 
     { 
      get { return "live file name"; } 
     } 
    } 


    public class CustomerLoader 
    { 
     [Inject] 
     public ICustomerRepository CustomerRepository { get; set; } 
     private string _fileName; 

     // To get ninject to add the constructor parameter uncomment the line below 
     //public CustomerLoader(string fileName) 
     //{ 
     // _fileName = fileName; 
     //} 
     public CustomerLoader(IFileNameContainer fileNameContainer) 
     { 
      _fileName = fileNameContainer.FileName; 
     } 

     public void LoadCustomer() 
     { 
      Customer c = CustomerRepository.GetCustomer(); 
      Console.WriteLine(string.Format("Name:{0}\nAge:{1}\nFile name is:{2}", c.Name, c.Age, _fileName)); 
     } 
    } 

    public interface ICustomerRepository 
    { 
     Customer GetCustomer(); 
    } 
    public class CustomerRepository : ICustomerRepository 
    { 
     public Customer GetCustomer() 
     { 
      return new Customer() { Name = "Ciaran", Age = 29 }; 
     } 
    } 
    public class Customer 
    { 
     public string Name { get; set; } 
     public int Age { get; set; } 
    } 
} 
+0

Guardando bene - questo è più o meno quello che intendevo. Nel contesto di un'app reale, suppongo che la roba IFilenameContainer potrebbe invece essere qualcosa di generale come DocumentLocation e che venga popolata al livello più alto da File | Open o dalla riga di comando, ecc. In generale, il concetto di wrapping delle informazioni che vengono trasmesse come dati grezzi in params in qualcosa che è possibile ottenere le informazioni da un livello superiore è ciò che rende l'utilizzo di parametri meno comuni (l'ho usato solo una volta nel mio ultimo progetto) –