Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 29 de septiembre de 2015
ASP.NET CoreDesde la creación de MVC, los helpers han sido piezas fundamentales en la composición de nuestras vistas. Llamadas como las habituales Html.ActionLink() o Html.TextBoxFor() nos han permitido durante años crear interfaces de forma sencilla y productiva, pues se trataba de métodos muy reutilizables capaces de ejecutar lógica de presentación y generar bloques de HTML por nosotros (o CSS, o incluso Javascript).

De hecho, los helpers han sido la fórmula recomendada para crear componentes reutilizables de generación de código de marcas en las páginas de nuestras aplicaciones MVC y son muy utilizados tanto por el propio framework como por componentes visuales de terceros. Pero esto no implica que fuera una solución perfecta o exenta de problemas…

Aunque en la mayoría de ocasiones daba la talla, había algunos escenarios relativamente frecuentes en los que la mezcla de código de servidor con código de marcado no quedaba demasiado elegante, como en el siguiente ejemplo:
@using (Html.BeginForm("LogOff", "Account", FormMethod.Post, 
        new { id = "logoutForm", @class = "navbar-right" }))
{
    @Html.AntiForgeryToken()
    <ul class="nav navbar-nav navbar-right">
        <li>
            @Html.ActionLink("Hello " + User.GetUserName() + "!", 
                             "Manage", "Account",
                             routeValues: null, 
                             htmlAttributes: new { 
                                 title = "Manage" 
                             })
        </li>
        <li>
           <button>Log off</button>
        </li>
    </ul>
}
Sin duda, es un código que no resultaría familiar a un desarrollador acostumbrado al mundo web porque no podría predecir a primera vista el marcado enviado el cliente y, en cualquier caso, la sintaxis resulta bastante farragosa cuando queremos personalizar un poco la salida HTML de los helpers que vienen de fábrica en MVC, por ejemplo cuando deseamos utilizar Bootstrap o cualquier otro framework basado en estilos.

Hey, antes de continuar, el tradicional disclaimer: ASP.NET Core MVC está aún en desarrollo (beta 8), y aún pueden cambiar algunas de las cosas que contemos aquí.

En ASP.NET Core MVC se ha dado una vuelta de tuerca más a la “inteligencia” del parser de Razor ofreciendo una fórmula alternativa para crear componentes reutilizables de generación de código de marcado desde el lado servidor, llamados Tag Helpers, que son igualmente sencillos de utilizar (incluso más, en algunos casos) y utilizan una sintaxis declarativa que se integra de forma más fluida en el interior de una vista. El siguiente bloque de código es equivalente al anterior, pero utilizando tag helpers:
<form asp-controller="Account" asp-action="LogOff" 
      method="post" id="logoutForm" class="navbar-right">
    <ul class="nav navbar-nav navbar-right">
        <li>
            <a asp-controller="Manage" asp-action="Index" 
               title="Manage">Hello @User.GetUserName()!</a>
        </li>
        <li>
            <button>Log off</button>
        </li>
    </ul>
</form>
De momento, veréis que apenas hay caracteres de escape de Razor, todo es código de marcado. De hecho, casi todo este código es marcado HTML convencional, salvo en las etiquetas <form> y <a>, que es donde estamos usando tag helpers.

Estos tags especiales son detectados por el motor de Razor y procesados en el lado servidor antes de que se genere la salida HTML que va al cliente. Los atributos “asp-“ que podéis ver es la forma que tenemos de enviar parámetros a tener en cuenta durante dicho proceso. Por ejemplo, en el caso del tag <a>, mediante los atributos asp-controller y asp-action estamos indicando respectivamente el controlador y acción cuya URL aparecerá en el atributo href que se enviará finalmente al cliente.

Por cierto, estos nombres de atributo utilizan el prefijo “asp-“ por convención, para distinguirlos de los atributos originales de la etiqueta e indicar al motor de Razor que se trata de un helper de este tipo, y obviamente no llegarán nunca al lado cliente.

Veamos otro ejemplo, esta vez en la generación de un hiperenlace, donde queda claro lo diferente que resulta leer el código si usamos helpers tradicionales frente a los nuevos tag helpers, siendo el resultado enviado al cliente exactamente el mismo:
@* Usando HTML helpers tradicionales *@
@Html.ActionLink("Hello " + User.GetUserName() + "!", 
                 "Manage", "Account",
                 routeValues: null, 
                 htmlAttributes: new { 
                    title = "Manage" 
                 })

@* Usando tag helpers *@
<a asp-controller="Manage" asp-action="Index" title="Manage">
   Hello @User.GetUserName()!
</a>

@* Resultado enviado al cliente *@
<a href="/manage" title="Manage">Hello, John!</a>
ASP.NET Core MVC viene de serie acompañado por un buen número de tag helpers, y, por supuesto, es posible añadir nuestras propias extensiones. Seguro que esto desembocará en un interesante ecosistema de este tipo de componentes desde la comunidad de desarrolladores.

¿Cómo es realmente un tag helper por dentro?

Como seguro podréis intuir, un tag helper es básicamente otra sintaxis para expresar la llamada a un helper, pero utilizando un modelo declarativo (usar etiquetas), en lugar de un modelo imperativo (la clásica llamada a Html.UnHelper()).

Debido a su naturaleza, estos helpers se definen de forma muy diferente a los tradicionales, cuya codificación se basaba prácticamente en implementar una función personalizada. Los tag helpers son clases que heredan de TagHelper y son decoradas con atributos que permiten asociarlas con tags HTML (existentes o no) y definir los atributos que deseamos gestionar desde el lado servidor antes de generar el marcado final que será enviado al cliente.

Para muestra, observad el siguiente código: se trata de un extracto del fuente de AnchorTagHelper, el tag helper que hace posible que Razor interprete de forma especial el tag <a>, tal y como hemos visto anteriormente. Como podemos observar, se utiliza [HtmlTargetElement] para indicar qué etiqueta HTML se ve afectado por este tag helper y los atributos de la etiqueta que deseamos procesar, que al fin y al cabo serían los parámetros que necesita el helper para funcionar. Para cada uno de esos atributos encontramos luego una propiedad en la clase; sus valores serán inyectados en tiempo de ejecución, y el HTML de salida se generará en el método Process() que vemos al final:
[HtmlTargetElement("a", Attributes = "asp-action"
[HtmlTargetElement("a", Attributes = "asp-controller")]
public class AnchorTagHelper : TagHelper
{
    public AnchorTagHelper(IHtmlGenerator generator)
    {
        Generator = generator;
    }

    protected IHtmlGenerator Generator { get; }

    [HtmlAttributeName"asp-action"]
    public string Action { get; set; }

    [HtmlAttributeName("asp-controller")]
    public string Controller { get; set; }

    public override void Process(TagHelperContext context, 
                                 TagHelperOutput output)
    {
        TagBuilder tagBuilder;
            tagBuilder = Generator.GenerateActionLink(
            linkText: string.Empty,
            actionName: Action,
            controllerName: Controller,
            htmlAttributes: null);
        output.MergeAttributes(tagBuilder);
    }
}
Fijaos que el código que encontramos en Process() es muy similar al que encontraríamos en un helper tradicional. La única diferencia es que aquí tenemos la oportunidad de que el HTML resultante sustituya por completo a la etiqueta que ha lanzado la ejecución del tag helper, que la modifique (cambiando sus atributos o contenido) o que la complemente (añadiéndole otras etiquetas por delante o por detrás).

Así, siguiendo con el ejemplo que hemos visto más arriba del tag <a>, la forma de procesarse este tag helper sería algo así:
  • Tenemos en el código de nuestra vista una etiqueta como:
    <a asp-controller="Manage" asp-action="Index" title="Manage">
       Hello @User.GetUserName()!
    </a>
  • El motor de Razor, en tiempo de compilación, detecta que la clase AnchorTagHelper afecta a la etiqueta <a> ,  por lo que, en vez de incluir dicha etiqueta directamente en el HTML de salida, la sustituye por una referencia a dicha clase. Sus propiedades Action y Controller son cargadas con los valores presentes en los atributos “asp-action” y “asp-controller” del tag, respectivamente, y se carga en el contexto del helper otra información interesante, como la etiqueta tag original, sus atributos o su contenido.   

  • Ya en tiempo de ejecución, se realiza una llamada al método Process(), que será el que finalmente generará el código de marcado. En este caso concreto, vemos que simplemente se generaría un nuevo tag <a> mediante la llamada a GenerateActionLink(), y se haría un merge con el tag original, de forma que se mantendrían el resto de atributos originales del enlace (por ejemplo, title).

Puesta en marcha de los Tag Helpers

Referencias hacia Microsoft.AspNet.Mvc.TagHelpersComo muchos otros aspectos, los tag helpers son un componente opcional en ASP.NET Core MVC, y para poder utilizarlos lo primero que debemos hacer es asegurarnos de que el proyecto tiene referencias hacia el paquete “Microsoft.AspNetCore.Mvc.TagHelpers”.

Tras ello, debemos habilitar su uso desde las vistas. La nueva directiva @addTagHelper de Razor permite añadir en las vistas referencias hacia los ensamblados que contienen estos componentes; por comodidad, el ideal es añadir las referencias en el archivo /Views/_ViewImports.cshtml, que ya hemos visto en un post anterior, de forma que las referencias se apliquen a todas las vistas de la aplicación.

El siguiente código, añadido al archivo /Views/_ViewImports.cshtml hará posible la utilización de los tag helpers proporcionados de fábrica por MVC desde todas las vistas de la aplicación:
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
Una vez añadida esta línea, podremos comenzar a utilizar estos componentes en nuestras vistas, y Visual Studio nos ayudará aplicando intellisense durante la edición:

Intellisense en tag helpers

Tag helpers disponibles “de serie” en MVC 6

A día de hoy (beta 7 lanzada, beta 8 en camino), tenemos disponibles de serie los siguientes tag helpers en Core MVC :
  • AnchorTagHelper
  • CacheTagHelper
  • EnvironmentTagHelper
  • FormTagHelper
  • ImageTagHelper
  • InputTagHelper
  • LabelTagHelper
  • LinkTagHelper
  • OptionTagHelper
  • ScriptTagHelper
  • SelectTagHelper
  • TextAreaTagHelper
  • ValidationMessageTagHelper
  • ValidationSummaryTagHelper
La mayoría de ellos se puede intuir para qué sirven porque ya teníamos helpers tradicionales que se utilizaban para ello: generación de vínculos, formularios, controles de edición, mensajes de validación… Otros, en cambio, definen nuevos tags (como <cache> o <environment>) que podemos utilizar en nuestras vistas. Para no hacer este post demasiado extenso, iremos viendo los más interesantes en artículos posteriores.

Eh, pero… ¿no es esto la reencarnación de los controles runat=”server” de toda la vida?

Pues la verdad es que al principio sí hay un cierto tufillo a ASP.NET WebForms y a sus célebres controles <asp:XXX runat="Server"> , pero no os preocupéis, esa sensación se pasa pronto ;D

En realidad estamos hablando de cosas bien distintas: aquí no hay ViewState, ni post backs, ni code behind, ni eventos, ni controles en el lado servidor, ni mantenimiento de estado de ningún tipo, es sólo generación de marcado. Simplificando un poco, los tag helper son sólo otra forma de introducir en las vistas llamadas a los helpers usando sintaxis declarativa, aunque con algunas ventajas adicionales sobre los tradicionales @Html.XXX() que hemos usado hasta ahora.

Publicado en Variable not found.

1 comentario:

Julio A dijo...

Hola José, como siempre un excelente post! Tag helpers es de las cosas que más me ha gustado, la verdad era que los helpers se veian algo feos, además de poco entendibles para el diseñador, ahora, eso permite tener un mayor control sobre el HTML, por ejemplo, cuando el diseñador dentro del a metía un label y dentro de ese label un span... adiós helpers (bueno si, lo podías crear), pero ahora con los tag helpers la cosa va muy diferente, y diferente de mejor :)

Claro, todavía quedán muchos otros helpers que toca seguir usando, pero por el momento me gusta!

Un saludo!