c# - injection - ninject mvc 5
¿Qué es Ninject y cuándo lo usas? (3)
He estado ayudando a algunos amigos en un proyecto y hay una clase que usa Ninject. Soy bastante nuevo en C # y no tengo idea de lo que está haciendo esa clase, por lo que necesito entender Ninject. ¿Alguien puede explicar qué es Ninject y cuándo lo usa (por ejemplo, si es posible)? O si puede señalar algunos enlaces que también serían geniales.
Intenté esta pregunta: ¿tutoriales / documentación de Ninject? pero realmente no ayudó a un principiante como yo.
Ninject es un contenedor de inversión de control.
¿Qué hace?
Supongamos que tiene una clase de Car
que depende de una clase de Driver
.
public class Car
{
public Car(IDriver driver)
{
///
}
}
Para usar la clase Car
, la compila de la siguiente manera:
IDriver driver = new Driver();
var car = new Car(driver);
Un containter de IoC centraliza el conocimiento sobre cómo construir clases. Es un repositorio central que sabe algunas cosas. Por ejemplo, sabe que la clase concreta que necesita usar para construir un automóvil es un Driver
y no cualquier otro IDriver
.
Por ejemplo, si está desarrollando una aplicación MVC, puede decirle a Ninject cómo construir sus controladores. Lo hace al registrar qué clases concretas satisfacen interfaces específicas. En tiempo de ejecución, Ninject descubrirá qué clases son necesarias para construir el controlador requerido, y todo detrás de las escenas.
// Syntax for binding
Bind<IDriver>().To<Driver>();
Esto es beneficioso porque le permite construir sistemas que son más fáciles de probar por la unidad. Supongamos que Driver
encapsula todo el acceso a la base de datos para el Car
. En una prueba unitaria para automóvil, puedes hacer esto:
IDriver driver = new TestDriver(); // a fake driver that does not go to the db
var car = new Car(driver);
Hay marcos enteros que se encargan automáticamente de crear clases de prueba para usted y se llaman marcos de burla.
Para más información:
Ninject es un inyector de dependencia para .NET, realización práctica del patrón Inyección de dependencia (forma de patrón de Inversión de control).
Supongamos que tiene dos clases de DbRepository
y Controller
:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
Entonces, ahora tienes dos problemas:
Debe inicializar
_repository
para usarlo. Usted tiene tales formas:- En constructor manualmente. Pero, ¿qué ocurre si el constructor de DbRepository cambia? Necesita volver a escribir su clase de
Controller
porque se modificó otra parte del código. No es difícil si solo tienes unController
, pero si tienes un par de clases que dependen de tuRepository
, tienes un problema real. Puede usar el localizador de servicios o la fábrica o smth. Ahora tiene dependencia en su localizador de servicios. Usted tiene un localizador de servicios globales y todo el código debe usarlo. ¿Cómo va a cambiar el comportamiento del localizador de servicios cuando necesita usar en una parte de la lógica de activación del código uno y algo más en otra parte del código? Solo hay una forma: pasar el localizador de servicios a través de los constructores. Pero con cada vez más clases tendrás que pasar cada vez más. De todos modos, esa es una buena idea, pero es una mala idea.
class Controller { private DbRepository _repository; public Controller() { _repository = GlobalServiceLocator.Get<DbRepository>() } // ... some methods that uses _repository }
Puede usar inyección de dependencia. Mira el código:
class Controller { private IRepository _repository; public Controller(IRepository repository) { _repository = repository; } }
Ahora, cuando necesite su controlador, escriba:
ninjectDevKernel.Get<Controller>();
oninjectTestKernel.Get<Controller>();
. Puede cambiar entre los resolvedores de dependencia tan rápido como desee. ¿Ver? Es simple, no necesitas escribir mucho.- En constructor manualmente. Pero, ¿qué ocurre si el constructor de DbRepository cambia? Necesita volver a escribir su clase de
No puedes hacer pruebas unitarias en él. Su
Controller
tiene dependencia deDbRepository
y si desea probar algún método que use repositorio, su código irá a la base de datos y le pedirá datos. Eso es lento, muy lento. Si su código enDbRepository
cambia, su prueba de unidad enController
se caerá. Solo la prueba de integración debe decir ''problemas'' en este caso. Lo que necesita en pruebas unitarias: para aislar sus clases y probar solo una clase en una prueba (en el ideal, solo un método). Si su códigoDbRepository
falla, usted pensará que el código delController
falló, y eso es malo (incluso si tiene pruebas paraDbRepository
yController
) ambos fallarán y usted puede comenzar desde el lugar equivocado. Lleva mucho tiempo determinar dónde está realmente el error. Debes saber que algunas clases están bien y otras clases fallaron.Cuando desee reemplazar
DbRepository
con algo diferente en todas sus clases, debe hacer un gran trabajo.No puede controlar fácilmente la vida útil de
DbRepository
. El objeto de esta clase crea al inicializar elController
y elimina cuando elController
elimina. No hay intercambio entre diferentes instancias de la claseController
y no hay intercambio entre otras clases. Con Ninject puedes simplemente escribir:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
Y una característica especial de la inyección de dependencia: ¡desarrollo ágil! Usted describe que su controlador usa el repositorio con la interfaz IRepository
. No es necesario que escriba DbRepository
, puede escribir una clase simple de MemoryRepository
y desarrollar Controller
mientras otros chicos desarrollan DbRepository
. Cuando DbRepository
, acaba de volver a vincular en su resolución de dependencias que el IRepository
predeterminado IRepository
es DbRepository
. ¿Has escrito muchos controladores? Todos ellos usarán ahora DbRepository
. Eso es genial.
Lee mas:
Otras respuestas son geniales, pero también me gustaría señalar este Implementing Dependency Injection usando el artículo de Ninject .
Este es uno de los mejores artículos que he leído que explica Dependency Injection y Ninject con un ejemplo muy elegante.
Aquí está el fragmento del artículo:
Debajo Interface será implementada por nuestro (SMSService) y (MockSMSService), básicamente la nueva Interfaz (ISMSService) expondrá los mismos comportamientos de ambos servicios como el siguiente código:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
(SMSService) implementación para implementar la interfaz (ISMSService):
public class SMSService : ISMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
Console.WriteLine("Sending SMS using gateway to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
(MockSMSService) con una implementación totalmente diferente usando la misma interfaz:
public class MockSMSService :ISMSService
{
public void SendSMS(string phoneNumber, string body)
{
SaveSMSToFile(phoneNumber,body);
}
private void SaveSMSToFile(string mobileNumber, string body)
{
/*implementation for saving SMS to a file*/
Console.WriteLine("Mocking SMS using file to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
necesitamos implementar un cambio en nuestro constructor de clase (UIHandler) para pasar la dependencia a través de él, al hacer esto, el código que usa el (UIHandler) puede determinar qué implementación concreta de (ISMSService) usar:
public class UIHandler
{
private readonly ISMSService _SMSService;
public UIHandler(ISMSService SMSService)
{
_SMSService = SMSService;
}
public void SendConfirmationMsg(string mobileNumber) {
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
Ahora, tenemos que crear una clase separada (NinjectBindings) que hereda de (NinjectModule). Esta clase será responsable de resolver las dependencias en tiempo de ejecución, luego anularemos el evento de carga que se utiliza para configurar el enlace. Lo bueno de Ninject es que no necesitamos cambiar nuestro código en (ISMSService), (SMSService) y (MockSMSService).
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<ISMSService>().To<MockSMSService>();
}
}
Ahora en el código de formulario de UI, usaremos el enlace para Ninject que determinará qué implementación usar:
class Program
{
static void Main(string[] args)
{
IKernel _Kernal = new StandardKernel();
_Kernal.Load(Assembly.GetExecutingAssembly());
ISMSService _SMSService = _Kernal.Get<ISMSService>();
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}
Ahora el código está utilizando Ninject Kernal para resolver toda la cadena de dependencias, si queremos usar el servicio real (SMSService) en modo Release (en entorno de producción) en lugar del simulacro, tenemos que cambiar en la clase de enlace Ninject ( NinjectBindings) solo para usar la implementación correcta o usando la directiva #if DEBUG como se muestra a continuación:
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
#if DEBUG
Bind<ISMSService>().To<MockSMSService>();
#else
Bind<ISMSService>().To<SMSService>();
#endif
}
}
Ahora nuestra clase vinculante (NinjectBindings) está viviendo en la parte superior de todos nuestros códigos de ejecución y podemos controlar la configuración fácilmente en un solo lugar.
Además, vea ¿Qué es la Inversión de Control? se mencionan algunos ejemplos muy simples para comprender IoC.