none
Ninject y Injección en Propiedades RRS feed

  • Pregunta

  • Buenas..

    Me encuentro en una situación bastante curiosa, y la verdad, no he encontrado manera alguna de solucionarla. Les planteo el problema a ver si alguien puede echarme un cable. Muchas gracias de antemano.El escenario es el siguiente : ASP.NET MVC 3 + Ninject MVC 3.

    Aqui les adjunto el ControllerFactory

    Public Class NinjectControllerFactory
            Inherits DefaultControllerFactory
            Private _ninjectKernel As IKernel
    
            Public Sub New()
                _ninjectKernel = New StandardKernel()
                AddBindings()
            End Sub
    
            Protected Overrides Function GetControllerInstance(requestContext As RequestContext, controllerType As Type) As IController
                Return If(controllerType Is Nothing, Nothing, DirectCast(_ninjectKernel.[Get](controllerType), IController))
            End Function
    
    
            Private Sub AddBindings()
                ' put additional bindings here 
                _ninjectKernel.Bind(Of IGestionUsuariosModel)().[To](Of GestionUsuariosModel)()
            End Sub
        End Class

    Cuando quiero "inyectar" la clase "GestionUsuariosModel" dentro de un Controller, no tengo ningun problema en definir el siguiente codigo...

        Public Class EmpresasController
            Inherits System.Web.Mvc.Controller
    
            <Inject()>
            Public Property GestionUsuariosModel As IGestionUsuariosModel
            '
            ' GET: /admin/Empresas
            Function Index() As ActionResult
                Return View(GestionUsuariosModel.GetAll)
            End Function
    End Class
    

    Esto funciona OK... Se instancia la clase GestionUsuariosModel, y cuando accedo a ella desde la accion Index, me devuelve lo que necesito, y listo..

    EL problema lo tengo, cunado quiero realizar la "injeccion" de la misma clase, en una clase externa (que no es un controller).

    Adjunto codigo.

    Public Class EmpresasViewModel
            
            Private _listaUsuarios As Dictionary(Of Guid, String) = Nothing
            
            Public Property ListaUsuarios As Dictionary(Of Guid, String)
                Get
                    Return _listaUsuarios
                End Get
                Set(value As Dictionary(Of Guid, String))
                    _listaUsuarios = value
                End Set
            End Property
    
            <Inject()>
            Public Property GestionUsuarios As IGestionUsuariosModel
    
            Public Sub New()
                listaUsuarios = GestionUsuarios.GetAllMemberShipUsersByRole("empresas")
            End Sub
    End Class

    Aqui... Cuando intento acceder a GestinUsuarios, veo que esta no se ha inciado (es Nothing)....


    POr que pasa esto???

    Puede ser que sea ;) porque mi clase NinjectControllerFactory, hereda de DefaultControllerFactory.????

    Como se deberia pues plantear esta situacion...

    Muchas gracias de antemano...





    viernes, 16 de marzo de 2012 15:27

Respuestas

  • >desconozco que exista alguna otra

    Pues sí, hay otra :)

    Cuando tienes un contenedor de IoC hay dos patrones que se suelen/pueden usar. Uno es dejar que el contenedor inyecte los elementos en el constructor o en las propiedades. Eso es DI. El otro consiste en usar directamente el contenedor para obtener instancias de determinados elementos. Eso es lo que se conoce como Service Locator. Pues bueno, el Service Locator es realmente un anti-patrón (http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx). Reconozco que hay discrepancias en este punto, hay gente que lo considera un patrón (yo no me cuento entre ellas :p).

    Tu estás usando ServiceLocator dentro de EmpresasViewModel porque usas el DependencyResolver para obtener una instancia de IGestionUsuarios. Así la pregunta es ¿se puede obtener DI, es decir que MVC nos inyecte IGestionUsuarios dentro de EmpresasViewModel? Y la respuesta es sí, se puede.

    Para ello tienes que hacer un par de cosillas adicionales: Definirte un custom model binder que use el DependencyResolver y registrarlo en el sistema. Ambas cosas son muy simples...

    El código de tu custom model binder puede ser algo como:

    public class DependencyModelBinder : DefaultModelBinder
    {
        protected override Object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            return DependencyResolver.Current.GetService(modelType);
        }
    }

    Luego debes indicar a ASP.NET MVC que use tu custom model binder para la creación de los viewmodels. Para ello en el Application_Start puedes poner:

    ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();

    Con esto estás indicando que el ModelBinder por defecto sea el DependencyModelBinder.

    Hay una opción intermedia, que es en lugar de establecer el DependencyModelBinder como ModelBinder por defecto, establecerlo solo para los objetos EmpresaViewModel:

    ModelBinders.Binders.Add(typeof(EmpresasViewModel), new DependencyModelBinder());

    Con eso ASP.NET MVC usará el DefaultModelBinder para todos los viewmodels, excepto para EmpresasViewModel. Si tienes varios tipos de viewmodels a los que quieres inyectar propiedades debes realizar n registraciones (o bien usar IModelBinderProvider).

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    • Propuesto como respuesta José M. AguilarMVP martes, 20 de marzo de 2012 18:20
    • Marcado como respuesta Eder Costa viernes, 26 de octubre de 2012 14:51
    martes, 20 de marzo de 2012 7:59

Todas las respuestas

  • > Puede ser que sea ;) porque mi clase NinjectControllerFactory, hereda de DefaultControllerFactory.????

    Sí :)

    Estas definiendo una factoría de controladores que se usa para crear controladores. Nada más. Los Viewmodels no son controladores y por lo tanto no son creados a través de la factoria de controladores :)

    Aquí hay varias cosillas a considerar, pero básicamente déjame preguntarte: quien instancia EmpresasViewModel???  Si lo estás usando como ViewModel tradicional, es decir como mero paso de datos entre vistas y controladores, este objeto te lo instanciará el ModelBinder. Por lo tanto, es normal que no se inyecte a tu viewmodel esta propiedad ya que no se está usando Ninject para crear el viewmodel.

    En MVC3 se usa una clase especial, DependencyResolver, para incorporar puntos de IoC en todo el framework. El DependencyResolver que se use, se puede acceder a través de la propiedad estática DependencyResolver.Current y se usa en varios sitios del framework, excepto por razones oscuras que se me escapan en el ModelBinder :(

    Tienes dos alternativas:

    1. Crearte un ModelBinder propio que use DependencyResolver.Current para crear los viewmodels, y registrar este ModelBinder como el por defecto. Lo más fácil sería derivar de DefaultModelBinder y redefinir CreateModel (ejemplo en http://stackoverflow.com/questions/9342686/one-custom-model-binder-to-createmodel-and-another-to-bindmodel).
    2. Crearte un ModelBinder propio que use el DependencyResolver. Crearte una clase que implemente IModelBinderProvider y registrarla en el DependencyResolver. Una vez hecho esto, automáticamente ASP.NET MVC usará tu model binder en lugar del default.

  • En ambos casos debes crearte tu propio DependencyResolver que use Ninject.

    En este post (sensacional) cuentan como hacerlo todo: http://www.triballabs.net/2011/09/guest-post-inversion-of-control-and-mvc-3/

    PD: Una vez te creas tu DependencyResolver la factoría de controladores deja de ser necesaria. La DefaultControllerFactory usa el DependenyResolver para crear los controladores. De hecho en MVC3 casi nunca es necesario crearse una factoría de controladores propia.

    Saludos.


  • Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

lunes, 19 de marzo de 2012 7:53
  • Gracias de antemano Eduard por responder..

    He seguido tus pasos, y he creado mi propio DependencyResolver.

    En principio, hasta aqui a funcionado "igual" todo.. quiero decir, que en mis Controllers, cuando le indicaba a una propiedad el atributo <inject()> esta se instanciaba con la clase que tenia registrada...

    Public Class EmpresasController 
    Inherits System.Web.Mvc.Controller 
    ' Todo sigue igual......!!!! :) 
    <Inject()> 
    Public Property GestionUsuariosModel As IGestionUsuariosModel 
    ' GET: /admin/Empresas 
    Function Index() As ActionResult 
    Return View(GestionUsuariosModel.GetAll) End Function 
    End Class

    El problema lo tenia cuando queria inyectar codigo en una clase modelo..

    Public Class EmpresasViewModel 
    Private _listaUsuarios As Dictionary(Of Guid, String) = Nothing 
    Public Property ListaUsuarios As Dictionary(Of Guid, String) 
    Get 
    Return _listaUsuarios 
    End Get 
    Set(value As Dictionary(Of Guid, String)) 
    _listaUsuarios = value 
    End Set End Property 
    <Inject()> 
    Public Property GestionUsuarios As IGestionUsuariosModel 
    
    Public Sub New() 
    listaUsuarios = gestionUsuarios.GetAllMemberShipUsersByRole("empresas") 
    End Sub 
    End Class

     He modificado pues esta ultima clase, para que me resuelva la instancia desde el propio DependencyResolver...Tal cual que así...

    Public Class EmpresasViewModel 
    Private _listaUsuarios As Dictionary(Of Guid, String) = Nothing 
    Public Property ListaUsuarios As Dictionary(Of Guid, String) 
    Get 
    Return _listaUsuarios 
    End Get 
    Set(value As Dictionary(Of Guid, String)) 
    _listaUsuarios = value 
    End Set 
    End Property 
    Public Property GestionUsuarios As IGestionUsuariosModel 
    Public Sub New() 
    ''' AQUI CODIGO AÑADIDO... ''' 
    GestionUsuarios = System.Web.Mvc.DependencyResolver.Current.GetService(Of IGestionUsuarios)() 
    listaUsuarios = GestionUsuarios.GetAllMemberShipUsersByRole("empresas") 
    End Sub 
    End Class
     Como funciona ;), entiendo que es una soluciona valida, aunque (dada mi poca experiencia con DI e IoC), desconozco que exista alguna otra... Muchas gracias de nuevo...
    lunes, 19 de marzo de 2012 12:53
  • >desconozco que exista alguna otra

    Pues sí, hay otra :)

    Cuando tienes un contenedor de IoC hay dos patrones que se suelen/pueden usar. Uno es dejar que el contenedor inyecte los elementos en el constructor o en las propiedades. Eso es DI. El otro consiste en usar directamente el contenedor para obtener instancias de determinados elementos. Eso es lo que se conoce como Service Locator. Pues bueno, el Service Locator es realmente un anti-patrón (http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx). Reconozco que hay discrepancias en este punto, hay gente que lo considera un patrón (yo no me cuento entre ellas :p).

    Tu estás usando ServiceLocator dentro de EmpresasViewModel porque usas el DependencyResolver para obtener una instancia de IGestionUsuarios. Así la pregunta es ¿se puede obtener DI, es decir que MVC nos inyecte IGestionUsuarios dentro de EmpresasViewModel? Y la respuesta es sí, se puede.

    Para ello tienes que hacer un par de cosillas adicionales: Definirte un custom model binder que use el DependencyResolver y registrarlo en el sistema. Ambas cosas son muy simples...

    El código de tu custom model binder puede ser algo como:

    public class DependencyModelBinder : DefaultModelBinder
    {
        protected override Object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            return DependencyResolver.Current.GetService(modelType);
        }
    }

    Luego debes indicar a ASP.NET MVC que use tu custom model binder para la creación de los viewmodels. Para ello en el Application_Start puedes poner:

    ModelBinders.Binders.DefaultBinder = new DependencyModelBinder();

    Con esto estás indicando que el ModelBinder por defecto sea el DependencyModelBinder.

    Hay una opción intermedia, que es en lugar de establecer el DependencyModelBinder como ModelBinder por defecto, establecerlo solo para los objetos EmpresaViewModel:

    ModelBinders.Binders.Add(typeof(EmpresasViewModel), new DependencyModelBinder());

    Con eso ASP.NET MVC usará el DefaultModelBinder para todos los viewmodels, excepto para EmpresasViewModel. Si tienes varios tipos de viewmodels a los que quieres inyectar propiedades debes realizar n registraciones (o bien usar IModelBinderProvider).

    Un saludo!


    Eduard Tomàs Blog: http://geeks.ms/blogs/etomas -- Twitter: eiximenis

    • Propuesto como respuesta José M. AguilarMVP martes, 20 de marzo de 2012 18:20
    • Marcado como respuesta Eder Costa viernes, 26 de octubre de 2012 14:51
    martes, 20 de marzo de 2012 7:59
  • Despues de tres dias (que ya lo tenia pendiente - mala cabeza -)....

    Muchas, Muchas gracias....

    viernes, 23 de marzo de 2012 15:06