¿Qué es ViewEngine en ASP.NET MVC?

Uno de los principales beneficios de crear una aplicación con ASP.NET MVC es que obtenemos mayor control en cuanto al código HTML se refiere. Aún así, es posible que al ver una vista generada para este patrón, resulte complicada la lectura para aquellos acostumbrados a trabajar con WebForms.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    if (Request.IsAuthenticated) {
%>
        Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
        [ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> ]
<%
    }
    else {
%>
        [ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%
    }
%>

Si bien este es un pequeño ejemplo del user control del LogOn que viene con la plantilla de ASP.NET MVC, la cosa se puede llegar a complicar bastante y puede dificultar su lectura y comprensión.

¿Qué es View Engine?

ASP.NET MVC utiliza un motor de vistas (View Engine) para reemplazar los métodos de renderizado de las aplicaciones por código HTML. Generalmente se está usando el proporcionado por ASP.NET MVC pero es posible utilizar otras alternativas creadas por la comunidad. Para ver un ejemplo de uno de los más conocidos, vamos a modificar una aplicación para utilizar Spark View Engine.

Configurar Spark View Engine

Nota: Para este ejemplo he utilizado una plantilla de ASP.NET MVC 1.0 ya que la versión actual de Spark no soporta ASP.NET MVC 2 RC 2.

La configuración es bastante simple. Para ello, debemos bajar la última versión del proyecto de CodePlex y adjuntar las siguientes librerías a nuestro proyecto:

Una vez que tenemos las librerías agregadas, necesitamos añadir Spark como motor de vistas a través del archivo Global.asax.

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ViewEngines.Engines.Add(new SparkViewFactory());
}

También es necesario generar un archivo donde especificamos las librerías necesarias para el renderizado. Este debe llamarse _global.spark y contedrá los siguientes assemblies (también puede definirse en el web.config).

<use namespace="System" />
<use namespace="System.Web.Mvc.Html" />
<use namespace="System.Collections.Generic" />
<use namespace="System.Linq" />

Esto sería lo mínimo necesario para comenzar a trabajar con Spark View Engine.

Las vistas utilizando Spark View Engine

Para que el motor de Spark reconozca la vistas como suyas, es necesario que las mismas tengan extensión .spark. Una cosa importante a tener en cuenta es que la master page toma un nombre distinto: Application.spark.  Si modificamos la master page por defecto de la plantilla de ASP.NET MVC quedaría de la siguiente manera:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>
        <use content="title">Default title</use>
    </title>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        <div id="header">
            <div id="title">
                <h1>
                    My MVC Application</h1>
            </div>
            <div id="logindisplay">
                <LogOnUserControl/>
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li>${Html.ActionLink("Home", "Index", "Home")}</li>
                    <li>${Html.ActionLink("About", "About", "Home")}</li>
                </ul>
            </div>
        </div>
        <div id="main">
            <use content="view" />
            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

Como diferencias podemos encontrar:

  1. Para declarar los ContentPlaceHolder utilizamos use content.
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <title>
        <use content="title">Default title</use>
    </title>
  2. Para incluir un control de usuario utilizamos tags con el nombre del mismo, por ejemplo <LogOnUserControl />
    <div>
        <% Html.RenderPartial("LogOnUserControl"); %>
    </div>
    <div>
        <LogOnUserControl/>
    </div>
  3.  En cuanto al uso de helpers, variables y todo aquello relacionado con el servidor, podemos utilizarlo con la misma sintaxis que hasta ahora, a excepción de <% %>. Con Spark View Engine utilizaremos ${}
    <ul id="menu">
        <li>${Html.ActionLink("Home", "Index", "Home")}</li>
        <li>${Html.ActionLink("About", "About", "Home")}</li>
    </ul>

Si queremos usar varias master pages en nuestra aplicación, basta con incluir la etiqueta <use master=”"/> en las vistas que lo requiera.

Los controles de usuarios también tienen extensión .spark y el nombre del archivo debe comenzar con guión bajo para ser reconocidos. Además es case sensitive cuando se utiliza el “modo tag”:

<LogOnUserControl />

Si no se tienen en cuenta estas dos indicaciones, no lo reconocerá y tampoco mostrará ningún tipo de error. Si quisieramos “traducir” el user control a lenguaje Spark quedaría de esta forma:

<if condition="Request.IsAuthenticated">
    Welcome <b>${Context.User.Identity.Name}</b>!
    [ !{Html.ActionLink("Log Off", "LogOff", "Account")} ]
</if>
<else>
    [ !{Html.ActionLink("Log On", "LogOn", "Account")} ]
</else>

Podemos crear condiciones a través de tags e intercalar código HTML con código del servidor de una forma, quizás, más clara.

Por último, comentar que para el caso de las vistas no utilizamos un guión bajo  al inicio para el nombre del archivo y que la extensión sigue siendo .spark, al igual que el resto de archivos.

<content name="title">
Index
</content>
<h2>
    Index</h2>
<p>
    This is the Message in ViewData: ${ViewData["Message"]}</p>
<p>
    ${Html.ActionLink("About us", "About")}</p>
<p>
    The time is ${DateTime.Now}.</p>

Además de Spark View Engine, disponemos de otros motores como pueden ser Brail, NVelocity, NHaml entre otros. A gusto del consumidor ;)

Adjunto el proyecto  por si fuera de utilidad.

¡Saludos!

Inyección de dependecias e Inversión de control

La verdad, no soy consciente de cuántos son los desarrolladores que conocen el significado de Inyección de Dependencias (Dependency Injection) o Inversión de control (Inversion of Control). Para ser sincera, nunca trabajé con ello en ningún proyecto real pero si he intentado recopilar información y conceptos para poder aplicarlos a mis proyectos personales.

Generalmente, cuando tenemos una clase que depende de otras para ciertas acciones, necesitamos inicializar instancias de las mismas para poder utilizarlas posteriormente. En ocasiones, una de las alternativas puede ser crear un objeto de dichas clases de forma privada e inicializarlas, utilizando el constructor de la clase principal.

Si vemos estas acciones desde el punto de vista de la Inyección de Dependencias y la Inversión de Control, no sería la forma más óptima debido a que la clase que sufre estas dependencias no debería ser la responsable de la creación de las mismas.

¿QUÉ CONSEGUIMOS?

  • Desacoplamiento.
  • Mejora la posibilidad de testeo de la aplicación.
  • Mejor mantenimiento a la hora de realizar cambios de los componentes, gracias a la modularidad.
  • [...]

Ambos conceptos están tan ligados que, en ocasiones, no se hace distinción. Se utiliza el concepto Inversión de Control para delegar en otro componente, un framework por ejemplo, la responsabilidad de crear las instancias necesarias en lugar de crearlas nosotros mismos. Por otro lado, la Inyección de Dependencias es el término utilizado cuando una clase depende de otra y, a través del constructor generalmente acepta un parámetro del tipo del cual depende. 

Para llevar a cabo el patrón de diseño de Inyección de Dependencias, es necesario el uso de interfaces y, lo más óptimo sería utilizar alguno de los frameworks disponibles para llevar a cabo la Inversión de Control. Algunos de estos frameworks son: Spring.NETWindsor Container, StructureMap, Unity, etcétera.

EJEMPLO A EVITAR

Por ver un ejemplo, supongamos que tenemos el siguiente código:

using System.Web.Mvc;
using IoC.Models;

namespace IoC.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        private readonly ITwitterService _twitterService;
        public HomeController()
        {
            _twitterService = new TwitterService();
        }

        public ActionResult Index()
        {
            return View(_twitterService.FetchTweets());
        }
    }
}

Tenemos un controlador, en una aplicación ASP.NET MVC, donde estamos haciendo uso de una librería que conecta con Twitter. Cuando se solicita la acción Index de este controlador, el controlador se crea, a través del constructor inicializa la variable _twitterService y realiza la llamada a FetchTweets. Esto funciona sin problemas, pero supone un inconveniente a la hora de realizar pruebas unitarias.

Por otro lado, si el día de mañana queremos, por ejemplo, utilizar otra clase que implemente ITwitterService bien porque hemos cambiado de librería, porque la forma de trabajar con Twitter es totalmente distinta, etcétera, deberíamos modificar a su vez este controlador(es) para modificar en el constructor la clase que implementa la interfaz a partir de ahora. Este es un caso bien simple pero ¿Y si nuestros controladores son dependientes de más de una clase y las mismas están en constante revisión, actualización, modificación, etcétera? La solución es bien simple:

using System.Web.Mvc;
using IoC.Models;

namespace IoC.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        private readonly ITwitterService _twitterService;
        public HomeController(ITwitterService twitterService)
        {
            _twitterService = twitterService;
        }

        public ActionResult Index()
        {
            return View(_twitterService.FetchTweets());
        }
    }
}

Gracias a la inyección de dependencias, liberamos al controlador de la carga de generar las instancias que necesita, lo abstraemos del tipo de clase que implementa la interfaz en este momento y conseguimos modularizar la aplicación.

PRUEBAS UNITARIAS

Si quisiéramos hacer un test, por ejemplo, que comprobara que al llamar a la acción Index el método FetchTweets es llamado, sin realizar la llamada real a Twitter e incluso sin hacer uso de la conexión que esto requiere a Internet, podríamos hacerlo de la siguiente manera:

using IoC.Controllers;
using IoC.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace IoC.Tests.Controllers
{
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void HomeController_AlLlamarALaAccionIndex_FetchTweetsEsLlamado()
        {
            //Arrange
            var mockTwitterService = MockRepository.GenerateMock<TwitterService>();
            mockTwitterService.Stub(m => m.FetchTweets()).Return(null);
            var homeController = new HomeController(mockTwitterService);

            //Act
            homeController.Index();

            //Assert
            mockTwitterService.AssertWasCalled(m => m.FetchTweets());
        }
    }
}

Nota: En este ejemplo he utilizado la librería RhinoMocks para crear la prueba unitaria.

Esto es realmente importante si queremos hacer pruebas unitarias de la aplicación, pero también es cierto que se nos presenta el siguiente problema ¿Cada vez que llame a HomeController voy a tener que encargarme y asegurarme en cada caso de que reciba una instancia de las interfaces que solicita el constructor? Aquí es donde entra en juego IoC y los numerosos frameworks existentes para este rol.

Para ver un pequeño ejemplo de cómo podríamos delegar esta funcionalidad en uno de los frameworks que soportan Inversión del Control, voy a utilizar StructureMap como demostración. Para descargar la última versión del framework podemos dirigirnos a este enlace.

CONFIGURANDO STRUCTUREMAP

En este ejemplo, vamos a configurar StructureMap de tal forma que sepa cómo actuar en el caso de requerir una instancia para una interfaz de tipo ITwitterService. Para ello, me he creado la siguiente clase:

using IoC.Models;
using StructureMap;

namespace IoC.StructureMapConfiguration
{
    public static class BootStrapper
    {
        public static void SetupContainer()
        {
            ObjectFactory.Configure(s => s.For<ITwitterService>().Use<TwitterService>());
        }
    }
}

En una sola línea, le estoy indicando que para la interfaz ITwitterService, debemos usar una instancia de la clase TwitterService. La magia de todo esto es que, si el día de mañana esta interfaz es implementada por otra clase, y además esta interfaz es usada en numerosos sitios de nuestra aplicación, solamente debemos modificar esta línea para que la clase que la implementa comience a servirse como dependencia en los casos que lo requiera.

CONTROLADORES ASP.NET MVC YSTRUCTUREMAP

Por otro lado, si lo que queremos es trabajar con ASP.NET MVC, debemos realizar una serie de cambios: Cuando nosotros hacemos una petición en una aplicación con ASP.NET MVC, la clase ControllerBuilder genera de forma automática el controlador solicitado, se despacha la petición y el controlador finaliza. Para poder utilizar las propiedades de StructureMap, necesitamos crear una clase que herede de la factoría de controladores. De esta manera, controlaremos el momento en el cual se solicita una instancia de un controlador y, si este tiene dependencias, poder administrarlas con la configuración realizada anteriormente para StructureMap en la clase BootStraper.

using System;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;

namespace IoC.StructureMapConfiguration
{
    internal class StructureControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return ObjectFactory.GetInstance(controllerType) as IController;

            return base.GetControllerInstance(requestContext, controllerType);
        }
    }
}

INICIALIZANDO LA CONFIGURACIÓN DE STRUCTUREMAP Y ASOCIADO LA NUEVA FACTORÍA DE CONTROLADORES

Para finalizar, necesitamos inicializar tanto la configuración creada en BootStraper, para que StructureMap reconozca la interfaz especificada, como la asignación de la nueva factoría de controladores a la aplicación ASP.NET MVC. La mejor ubicación para este caso concreto, podría ser el archivo Global.asax.

using System.Web.Mvc;
using System.Web.Routing;
using IoC.StructureMapConfiguration;

namespace IoC
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode,
    // visit <a href="http://go.microsoft.com/?LinkId=9394801">http://go.microsoft.com/?LinkId=9394801</a>

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}/{action}/{id}",                           // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterRoutes(RouteTable.Routes);

            BootStrapper.SetupContainer();

            ControllerBuilder.Current.SetControllerFactory(new StructureControllerFactory());
        }
    }
}

Si arrancamos la aplicación, comprobaríamos que efectivamente se crea un controlador con su dependencia y delegamos esta acción gracias a la Inversión de Control.

Gracias a Hadi Hariri por sus enseñanzas :)

¡Saludos!