lunes, 19 de agosto de 2013

Refrito de helpers en ASP.NET MVC

A propósito de la creación de Helpers en proyectos de ASP.NET MVC, se ha escrito mucho y además se ha escrito bien.

En Variable Not Found de José María Aguilar

Helpers locales en vistas ASP.NET MVC

Helpers locales MVC con Razor

 Y más sobre helpers en Razor

 

En Burbujas en .NET de Eduard Tomàs

ASP.NET MVC3: Razor Templates

ASP.NET MVC3: Un helper Repeater

¿Por qué usar los helpers para formularios?

 

En programandonet por Luis Ruiz Pavón

Creando HTML Helpers personalizados en ASP.NET MVC 3

En el blog de Javier Torrecilla

Los helpers en MVC

Dicho esto y asumiendo que me dejo seguro muchos posts valiosísimos en el tintero (tú me lo dices y yo te pongo), en este post lo que quiero es escribir un pequeño resumen de los distintos tipos de helpers que podemos crear en un proyecto de ASP.NET MVC.

Para los siguientes ejemplos asumiremos un sencillo código HTML donde queremos parametrizar el título y el contenido:

<div>

    <h1>Título</h1>

    <p>Contenido</p>

</div>

Helper global

  • También llamada external helper o simplemente helper de toda la vida.
  • Método de extensión de la clase HtmlHelper.
  • Devuelve una instancia de IHtmlString (por defecto ya hay una implementación que es HtmlString).
    • Si quieres ver porque HtmlString y no MvcHtmlString, en el siguiente enlace lo explican http://stackoverflow.com/a/4596957
    • En cualquier caso, no es obligatorio que un helper devuelve siempre código HTML, también podría devolver cualquier otro tipo de datos, por ejemplo un bool o simplemente no devolver nada (void). Además incluso podría devolver directamente un string y entonces Razor codificaría en HTML la salida (con HtmlString, Razor confía en los helpers y no codifica en HTML la salida).
  • Utilizar la clase TagBuilder para construir el DOM. A mí sinceramente no me gusta mucho esta clase. Además de no ser fluida, lo más importante es que no tiene ninguna relación de parentesco entre elementos. Claramente no es la mejor clase que hayan hecho la gente de MVC pero tampoco parece haber muchas más alternativas, quizás sólo HtmlTextWriter o escribiendo HTML directamente con String.Format.
  • Accesible por cualquier vista que importe el espacio de nombres donde esté declarado el helper.
    • A este propósito, también pueden incluir el espacio de nombres en el fichero Views/Web.config y estará automáticamente disponibles para todas las vistas.

using System.Web;

using System.Web.Mvc;

 

namespace MvcApplication1.Extensions

{

    public static class HtmlHelpers

    {

        public static IHtmlString TituloYContenido(

            this HtmlHelper htmlHelper,

            string titulo,

            string contenido)

        {

            var div = new TagBuilder("div");

            var h1 = new TagBuilder("h1");

            h1.InnerHtml = titulo;

            var p = new TagBuilder("p");

            p.InnerHtml = contenido;

            div.InnerHtml = h1.ToString() + p.ToString();

            return new HtmlString(div.ToString());

        }

    }

}


Para utilizar este helper en cualquier vista, bastaría con importar el espacio de nombres donde se declaró el helper:

@using MvcApplication1.Extensions

@Html.TituloYContenido("panicoenlaxbox", "Sergio León")

Si quisiéramos escribir el anterior helper con HtmlTextWriter en vez de con TagBuilder, el código sería el siguiente:

using System.IO;

using System.Web;

using System.Web.Mvc;

using System.Web.UI;

 

namespace MvcApplication1.Extensions

{

    public static class HtmlHelpers

    {

        public static HtmlString TituloYContenido(

            this HtmlHelper htmlHelper,

            string titulo,

            string contenido)

        {

            var writer = new HtmlTextWriter(new StringWriter());

            writer.RenderBeginTag("div");

            writer.RenderBeginTag("h1");

            writer.Write(titulo);

            writer.RenderEndTag();

            writer.RenderBeginTag("p");

            writer.Write(contenido);

            writer.RenderEndTag();

            writer.RenderEndTag();

            return new HtmlString(writer.InnerWriter.ToString());

        }

    }

}


Helper local

En ASP.NET MVC tenemos disponibles 2 tipos distintos de helper locales:

·         Normales

·         Declarativos

Los helper normales son muy parecidos a los helper globales:

·         Se declararan en la propia vista en un bloque @functions

@functions

{

    HtmlString TituloYContenido(

        string titulo,

        string contenido)

    {

        var div = new TagBuilder("div");

        var h1 = new TagBuilder("h1")

        {

            InnerHtml = titulo

        };

        var p = new TagBuilder("p")

        {

            InnerHtml = contenido

        };

        div.InnerHtml =

            h1.ToString() + p.ToString();

        return new HtmlString(div.ToString());

    }

}

Para usar este helper local en la propia vista donde fue definido:

@TituloYContenido("panicoenlaxbox", "Sergio León")

Los helpers locales declarativos (también llamadas inline helpers) van un paso más allá y permiten mezclar código de servidor y HTML en el propio helper.

·         Se declaran en la propia vista en un bloque @helper

·         No pueden devuelven ningún tipo de dato.
En realidad devuelven un HelperResult pero para nosotros es totalmente transparente.

·         Su código se escribe directamente en la vista

·         Sirven exclusivamente para generar HTML (a diferencia de los helpers locales normales que podían devolver cualquier tipo de dato).

@helper TituloYContenido(string titulo, string contenido)

{

    <div>

        <h1>@titulo</h1>

        <p>@contenido</p>

    </div>

}

Su utilización es igual a como llamaríamos a un helper local normal:

@TituloYContenido("panicoenlaxbox", "Sergio León")

Si tenemos Resharper podemos refactorizar código de la vista en un helper local declarativo:

clip_image001

Por último, me gustaría mostrar un ejemplo donde usando la carpeta especial App_Code, podemos crear helpers locales (@functions y @helper) con ámbito global. La idea está tomada de este post donde explica esto y otras cosas The Difference Between @Helpers and @Functions In WebMatrix.

Lo primero es crear la carpeta App_Code y agregar un fichero MyFunctions.cshtml y MyHelpers.cshtml. El nombre del fichero es importante puesto que después será el nombre por el que hagamos referencia a los helpers allí escritos.

clip_image002

El contenido de MyFunctions.cshtml es el siguiente:

@using System.Web.Mvc

@functions {

    public static HtmlString BotonReestablecer(string text)

    {

        var input = new TagBuilder("input");

        input.Attributes.Add("type", "reset");

        input.Attributes.Add("value", text);

        return new HtmlString(

            input.ToString(TagRenderMode.SelfClosing));

    }

}

Lo más reseñable es que hemos tenido que hacer el helper local, público y estático.

Por otro lado, MyHelpers.cshtml es como sigue:

@helper BotonEnviar(string text)

{

    <input type="submit" value="@text"/>

}

Ahora, en cualquiera de las vistas de nuestro proyecto podemos escribir lo siguiente:

@MyHelpers.BotonEnviar("Enviar")

@MyFunctions.BotonReestablecer("Reestablecer")

Algo bastante útil a la hora de trabajar con helpers declarativos en App_Code es obtener acceso a la vista actual para poder utilizar todos sus métodos y propiedades (Html, Url, etc.) a través de
((WebViewPage)WebPageContext.Current.Page);

Después de esto y con el chorro de enlaces que tiene el post, espero que pegarte con los helpers de MVC sea coser y cantar!

Un saludo!

3 comentarios:

  1. Completo el post, pero deberias de haber incluido algo sobre en que carpetas se deben de alojar cada tipo de helpor por convencion. Pero en genral muy educativo para gente como yo que esta empezando en ASP MVC

    ResponderEliminar
    Respuestas
    1. Me parece buena idea. Yo ahora mismo tengo una carpeta Extensions en la raíz del proyecto web con ficheros como HtmlExtensions, UrlExtensions. Es decir, un fichero para cada Helper que extiendo. En la carpeta App_Code he creado un fichero AppFunctions y otro AppHelpers. En realidad, esto va en el gusto de cada uno. Lo mejor es bajarse código de codeplex, github o sitios de estos y leer mucho de código de otros. De hecho, seguro que en un tiempo cuando vuelva a leer este comentario, mi estructura de carpetas y ficheros será distinta! :)
      Un saludo.

      Eliminar
  2. Felicidades por tan excelso aporte!!!

    ResponderEliminar