Inyección de parámetros en acciones ASP.NET MVC (y III)

ASP.NET MVCEn este tercer y último post de la serie (ir al primero o segundo) , vamos a ver una última técnica para realizar inyección de parámetros a acciones ASP.NET MVC que, aunque aporta menos a la hora de comprender las misteriosas interioridades del framework, es ciertamente mucho más cómoda y práctica en caso de que deseemos aplicar esta técnica.

Recapitulando un poco, queremos pasar de un planteamiento en el que el controlador es el que recibe mediante su constructor todas las dependencias que necesitan sus acciones, a otro en el que sean las propias acciones las que reciben los componentes de los que dependen.

Por tanto, pasaríamos de esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HomeController: Controller
{
    private readonly INotificationServices _notificationServices;
    private readonly IProductServices _productServices;
 
    public HomeController(INotificationServices notificationServices,
                          IProductServices productServices)
    {
        _notificationServices = notificationServices;
        _productServices = productServices;
    }
 
    [HttpPost]
    public ActionResult Contact(ContactViewModel vm)
    {
        if (!ModelState.IsValid) return View();
        _notificationServices.NotifyContact(vm.Name, vm.Email, vm.Text);
        return RedirectToAction("Thankyou");
    }
 
    public ActionResult Products()
    {
        return View(_productServices.GetAll());
    }
 
    [...]
}

A esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HomeController: Controller
{
    public HomeController()
    {
    }
 
    [HttpPost]
    public ActionResult Contact(ContactViewModel vm,
                                INotificationServices notificationServices)
    {
        if (!ModelState.IsValid) return View();
        notificationServices.NotifyContact(vm.Name, vm.Email, vm.Text);
        return RedirectToAction("Thankyou");
    }
 
    public ActionResult Products(IProductServices productServices)
    {
        return View(productServices.GetAll());
    }
 
    [...]
}

Pero ojo, antes de continuar, un pequeño disclaimer:

Esta técnica no es especialmente recomendable en la mayoría de escenarios, puesto que puede ocultar dependencias y propiciar la aparición de controladores extensos y con demasiadas responsabilidades. Aún así, es un ejercicio interesante para conocer los mecanismos de funcionamiento internos del framework ASP.NET MVC.

En los artículos anteriores hemos visto cómo conseguirlo manualmente usando dos puntos de extensibilidad del framework, el action invoker y el model binder. En esta ocasión vamos a ver cómo Autofac, el popular contenedor de IoC, nos lo da prácticamente solucionado.

Lo primero que tenemos que hacer, como siempre, es instalar la extensión específica para MVC 4 a través de Nuget:

PM> install-Package Autofac.Mvc4
Attempting to resolve dependency 'Autofac (≥ 3.0.0)'.
Installing 'Autofac.Mvc4 3.0.0'.
Successfully installed 'Autofac.Mvc4 3.0.0'

A continuación, creamos una clase en App_Start para configurar los aspectos relativos al contenedor de inversión de control e inyección de dependencias:

public static class IocConfig
{
    public static void Setup()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ProductServices>()
               .As<IProductServices>().InstancePerHttpRequest();
        builder.RegisterType<NotificationServices>()
               .As<INotificationServices>().InstancePerHttpRequest();

        builder.RegisterType<ExtensibleActionInvoker>()
               .As<IActionInvoker>()
               .WithParameter("injectActionMethodParameters", true);

        builder.RegisterControllers(Assembly.GetExecutingAssembly())
               .InjectActionInvoker();

        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 
    }
}

En el código anterior encontramos:

  • En primer lugar, el registro de los interfaces IProductServices e INotificationServices, para que puedan ser resueltos correctamente.  
  • A continuación se registra la clase ExtensibleActionInvoker, propia de Autofac, como el action invoker a usar en nuestra aplicación. Se le indica, además, que debe inyectar valores para los parámetros de las acciones, que es justo lo que andábamos buscando.  
  • Por último, se registran los controladores con el nuevo action invoker, y se establece como dependency resolver del sistema el facilitado también por Autofac.

Una vez hecho esto, sólo necesitamos llamar al método Setup() desde el global.asax.cs para que se ejecute al arrancar el sistema, ¡y esto es todo! Con esos simples pasos tendremos funcionando nuestra aplicación ASP.NET MVC con inyección de parámetros automática, utilizando para su resolución el contenedor de IoC Autofac.

El ejemplo desarrollado en este post podéis descargarlo desde Skydrive.

Publicado en http://www.variablenotfound.com/2013/06/inyeccion-de-parametros-en-acciones_18.html

Inyección de parámetros en acciones ASP.NET MVC (II)

ASP.NET MVCEn el primer post de la serie, vimos rápidamente en qué consiste la inyección de parámetros como fórmula para suministrar dependencias a una acción ASP.NET MVC, y realizamos una implementación sustituyendo unas pequeñas piezas del framework llamados action invokers que, como su nombre indica, son los responsables de invocar las acciones solicitadas por el usuario.

La idea es poder tener en nuestros controladores acciones que reciben directamente un interfaz que es resuelto justo en el momento de ejecutarla, es decir, algo así:

1
2
3
4
public ActionResult Products(IProductServices productsServices)
{
    return View(productsServices.GetAll());
}

Si intentamos ejecutar directamente esta acción en ASP.NET MVC, veremos que aparece un error indicando que no se puede crear una instancia de un interfaz. Lo que pretendemos conseguir, esta vez utilizando model binders, es que cuando se detecte un parámetro de este tipo, se resuelva la dependencia utilizando, por ejemplo, un contenedor IoC.

Pero antes de ver cómo, va una recomendación:

Disclaimer: como ya comenté en el artículo anterior, esta técnica no es especialmente recomendable en la mayoría de escenarios, puesto que puede ocultar dependencias y propiciar la aparición de controladores extensos y con demasiadas responsabilidades. Aún así, es un ejercicio interesante para conocer los mecanismos de funcionamiento internos del framework ASP.NET MVC.

Inyección de parámetros usando model binders

Los model binders son los componentes usado por el framework para obtener los valores para cada uno de los parámetros requeridos por una acción. Es decir, si en una acción tenemos un parámetro de tipo XYZ, será un model binder el que se encargue de buscar valores para el mismo en el contexto de la petición apoyándose a su vez en otros componentes llamados value providers, aunque para nuestros objetivos éstos no tienen importancia.

Por defecto, el proceso de binding de ASP.NET MVC es realizado por la clase DefaultModelBinder, aunque es posible, y además bastante fácil, crear una implementación alternativa y tomar el control durante este proceso simplemente heredando de ella y sobrescribiendo los métodos que nos interesen.

En particular, nos interesaría tomar el control en el momento de creación de las instancias de los parámetros de tipo referencia, que se encuentra en el método CreateModel() del binder. En ese punto podríamos comprobar si se está intentando construir un objeto a partir de un tipo interfaz, para proporcionárselo nosotros a través del dependency resolver y evitar el error que comentábamos antes.

Bien, pues tan sencillo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DependencyInyectorModelBinder: DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext,
                                          ModelBindingContext bindingContext,
                                          Type modelType)
    {
        if (modelType.IsInterface)
        {
            return DependencyResolver.Current
                                     .GetService(modelType);
        }
        else return base.CreateModel(controllerContext,
                                     bindingContext, modelType);
    }
}

El siguiente paso, como en otras ocasiones, sería hacer que el framework utilice este binder, para lo cual sólo tenemos que introducir el siguiente código para que sea ejecutado durante la inicialización del sistema:

1
ModelBinders.Binders.DefaultBinder = new DependencyInyectorModelBinder();

Observad que en este momento, cada vez que el binder se encuentre con un parámetro de tipo interfaz, acudirá al dependency resolver para intentar obtener una instancia. Lo último que tenemos que hacer sería encargarnos de dicha resolución.

En el post anterior vimos cómo hacerlo directamente implementando un dependency resolver personalizado, pero podemos hacerlo incluso de forma más fácil si utilizamos Unity o cualquier otro contenedor de inversión de control. En este caso, podríamos instalar el paquete Unity.Mvc y registrar nuestras clases de la forma habitual:

1
2
3
4
5
6
7
public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<IProductServices, ProductServices>(
                    new PerRequestLifetimeManager());
    container.RegisterType<INotificationServices, NotificationServices>(
                    new PerRequestLifetimeManager());
}

A diferencia del artículo anterior, en este caso sí estaríamos gestionando correctamente el ciclo de vida de las dependencias, puesto que serían liberadas automáticamente al finalizar la petición. Lo único que no debemos olvidar es introducir también esta inicialización del contenedor en el proceso de arranque de la aplicación.

Resumen y próximos pasos

En este artículo hemos visto otra posibilidad para implementar la inyección de parámetros en acciones ASP.NET MVC, utilizando esta vez model binders, dado que son éstos los encargados de crear las instancias de las clases que son suministradas como parámetros a los métodos. Como siempre, hemos podido comprobar lo sencillo que resulta modificar la implementación que viene de serie en el framework para adaptarlo a nuestras necesidades.

Sin embargo, ciertamente es bastante trabajoso, y más aún teniendo en cuenta que hay ya herramientas que nos lo dan solucionado. En el próximo post, el último de la serie, veremos una de ellas 😉

El ejemplo desarrollado en este post podéis descargarlo desde Skydrive.

Publicado en Variable not found.