2009-08-18 11 views
17

Soy un novato cuando se trata de DI y ninject y estoy luchando un poco acerca de cuándo debería ocurrir la inyección real y cómo iniciar el enlace .Ninject - cómo y cuándo inyectar

Lo estoy usando ya en mi aplicación web y funciona bien allí, pero ahora quiero usar la inyección en una biblioteca de clases.

Decir que tengo una clase como esta:

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 
    } 

} 

Quiero usar la inyección propiedad aquí porque tengo que pasar en un nombre de archivo en mi constructor. ¿Estoy en lo correcto al decir que si necesito pasar un parámetro de constructor, no puedo usar la inyección de constructor? Si puedo usar la inyección del constructor con parámetros adicionales, ¿cómo paso esos parámetros?

Tengo una aplicación de consola que consume por clase de prueba que se ve tan sigue:

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

Mi problema es que cuando llamo test.ImportData() mis repositorios son nulos - nada se ha inyectado en ellos. He intentado crear otro módulo y llamar

Bind<TestClass>().ToSelf(); 

como pensé que esto podría resolver todas las propiedades de inyección en TestClass pero estoy llegando a ninguna parte.

Estoy seguro de que este es un problema trivial, pero parece que no puedo averiguar cómo hacerlo.

Respuesta

18

Usted está directamente Newing TestClass, que Ninject no tiene manera de interceptar - recuerde que no hay magia como la transformación de código interceptando su new s etc.

que debería estar haciendo kernel.Get<TestClass> lugar.

De no ser así, puede inyectarse después quenew con un kernel.Inject(test);

Creo que hay un artículo en el wiki que habla de Inject vs Get etc.

Tenga en cuenta que, en general, directa Get o Inject las llamadas son un olor Doing It Wrong de la ubicación del servicio, que es un antipatrón. En el caso de su aplicación web, NinjectHttpModule y PageBase son el gancho que intercepta la creación de objetos: existen interceptores/lugares lógicos similares para interceptar en otros estilos de aplicación.

Re su Bind<TestClass>().ToSelf(), generalmente un StandardKernel tiene ImplicitSelfBinding = true lo que haría innecesario que (a menos que quiera influir en su alcance a ser algo más que .InTransientScope()).

Un último punto de estilo: - está usando la inyección de propiedades. Rara vez hay buenas razones para esto, por lo que debería usar inyección de constructor.

Y ve a comprar Dependency Injection in .NET by @Mark Seemann, que tiene muchas publicaciones excelentes por aquí que cubren muchas consideraciones importantes pero sutiles dentro y alrededor del área de Inyección de Dependencia.

+0

Usando kernel.Inject (test) parece funcionar. Gracias. Por curiosidad, otra pregunta - Estoy renovando TestClass porque necesito pasar una cadena al constructor. ¿Hay alguna manera de pasar un parámetro usando kernel. Obtener ()? –

+0

Sí - hay sobrecargas de Get y también puede ponerlas en el Bind –

+0

Generalmente una mejor patttern en lugar de cablearla así es tener lo que necesita el arg constructor en lugar de referencia un objeto confuigurator que se acaba y luego se une cuando se crea el módulo –

7

OK,

que he encontrado la manera de hacer lo que necesito, gracias en parte a sus comentarios Ruben. Creé un nuevo módulo que básicamente contiene la configuración que uso en la biblioteca de la clase. Dentro de este módulo, puedo enlazar utilizando una interfaz de marcador de posición o puedo agregar un parámetro de constructor al CustomerLoader. A continuación se muestra el código de una aplicación de consola ficticia para demostrar ambas formas.

¡Esto podría ayudar a alguien más a comenzar 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

Luce bien - eso es más o menos lo que quise decir. En el contexto de una aplicación real, supongo que el material de IFilenameContainer podría en lugar de algo general como DocumentLocation, y se llenaría en el nivel superior desde el archivo | Abrir o la línea de comandos, etc. En general, el concepto de envolver la información que se está pasando como datos sin procesar en params en algo de lo que puedes obtener la información a un nivel más alto es lo que hace que el uso de parámetros sea menos común (solo lo he usado una vez en mi último proyecto) –

Cuestiones relacionadas