Creating and Using a Helper in an ASP.NET Web Pages (Razor) Site

HTMLHelpers

Ayer vimos las funciones relacionadas con las direcciones. Hoy nos centramos en las que realmente nos van a ayudar en el trabajo diario, las que nos generan código HTML.
Aunque el listado es mucho más amplio que el de URLHelpers, lo cierto es que todos son muy parecidos.
Vamos a comenzar con el elemento básico que es el formulario que recoge todos los campos con los que trabajamos.
Nos creamos una método / acción en nuestro controlador para ir jugando. Le podemos llamar helpers. No le vamos a pasar ningún modelo por el momento.
Editamos su vista y lo básico para un formulario en HTML es <form ...></form>.
La idea es que no lo escribamos directamente sino que usemos los helpers.
BeginForm(s:action, s:controller, o:values)
Si escribimos
@using (Html.BeginForm()) {

}

Nos generaría el siguiente código
<form action="/Home/helpers" method="post"></form>
Por defecto nos genera una acción a nosotros mismos y hace la llamada con método post
Con los parámetros podemos indicar que controlador / acción del action y el método
Html.BeginForm("Create", "Libro", FormMethod.Get)

Y generaría
<form action="/Libro/Create" method="get"></form>

Todos los HTMLHelpers incluyen un último parámetro abierto a añadir cualquier atributo HTML, por ejemplo id, class, etc. En el caso de que queramos indicar el id
Html.BeginForm("Create", "Libro", FormMethod.Get, new {id="fTest"}))
Si además quisiéramos añadir el estilo con class al ser una palabra reservadas del sistema debemos escribir la propiedad con @class.
Html.BeginForm("Create", "Libro", FormMethod.Get, new {id="fTest", @class="formulario-ua"})

El resultado final sería
<form action="/Libro/Create" class="formulario-ua" id="fTest" method="get"></form>

En caso de que no usemos using en la declaración de beginform deberemos usar endform para indicar donde acaba.
@{ Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"}); }
...

@{ Html.EndForm();}

Yo usaré en todos los ejemplos using porque queda el código más agrupado.
Ahora es el momento de incluir elementos en el formulario
Label(s:name, o:text)
Un etiqueta con texto que hace referencia a un campo (name). En caso de que  no se indique el texto pondrá por defecto el nombre del campo que hayamos indicado en el primer parámetro.
@using(Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"}))
{
    @Html.Label("Nombre", "Nombre:")
    ...
}
En este ejemplo mostrará una etiqueta "Nombre:" que hace referencia a un campo "Nombre".
<label for="Nombre">Nombre:</label>

TextBox(s:name, o:value)
Crea una caja de texto con el nombre que le indiquemos  y con un valor por defecto en el segundo parámetro.
@Html.TextBox("Nombre", "Alberto")

genera
<input id="Nombre" name="Nombre" type="text" value="Alberto" />

Todo el contenido que se asigne en el valor por defecto (en cualquier HTMLHelper) se codifica automáticamente para evitar que se produzcan ataques XSS injection.
CheckBox(s:name, b:checked)
Genera una elemento checkbox.

DropDownList(s:name, list:selectlistitems)
Genera una lista desplegable en la que podemos seleccionar un único elemento.
    @Html.Label("Sexo")
    @Html.DropDownList("Sexo", new MultiSelectList(new[] {"Hombre", "Mujer"}))

Genera
Resultado de añadir un DropDownList
Hidden(s:name, o:value)
Genera un campo oculto.

ListBox(s:name, list:selectlistitems)
Genera una lista de valores en la que podemos seleccionar más de un elemento.
@using(Html.BeginForm("Create", "Libro", FormMethod.Get, new {id = "fTest", @class = "formulario-ua"}))
{
    @Html.Label("Unidad")
    @Html.ListBox("Unidad", new MultiSelectList(new[] {"Servicio de Informática", "Selección y Formación", "Servicio de Personal"}))
}

Genera
 Resultado de añadir un ListBox
RadioButton(s:name, o:value, b:checked)
Genera una elemento radiobutton

TextArea(s:name, s:value)
Crea una caja de texto de tipo textarea.

ValidationSummary([Exclude property-level error])
Muestra una lista no ordenada de todos los errores que se producen al validar el formulario.
Se puede validar todo o excluir los errores a nivel de las propiedades del modelo
Los errores se pueden lanzar en tiempo de ejecución con la propiedad AddModelError(campo, mensaje de error)  del objeto ModelState.
En caso de que el campo lo dejemos vacío estamos lanzando un error a nivel de modelo
            ModelState.AddModelError("", "Prueba de un error general");

y si indicamos el campo lo hacemos a nivel de propiedad.
            ModelState.AddModelError("Nombre", "El nombre no cumple con los requisitos");

Añadimos a la vista un sumario de validación
@using(Html.BeginForm())
{
    @Html.ValidationSummary(false)
}

El resultado
Incluir un sumario con todos los errores
Por defecto asigna el estilo "validation-summary-errors”.

ValidationMessage (s:[Nombre del campo], o:[Mensaje de error])
En caso de que no queramos que sea un sumario el que recoja todos los mensajes, si no que cada mensaje aparezca en el punto que indiquemos (normalmente a la derecha del campo), usaremos este Helper.
Si no se especifica el mensaje de error, todos aquellos errores que se produzcan (o provoquemos) se visualizarán en este punto.
    @Html.ValidationMessage("Nombre")

Se visualizará de la siguiente manera
Generar un mensaje de validación dinámico
Por defecto asigna el estilo “field-validation-error”.

Action(s: [nombre acción])
Nos permite llamar a un método / acción de un controlador. Puede parecerse mucho a las vistas parciales que vimos ayer y que recordaremos luego, pero lo cierto es que mientras las vistas parciales están pensadas para escribir bloques de código, action está orientada a ejecutar el proceso completo de la acción de un controlador (que incluye la generación del código con la Vista).
El resultado es una cadena de texto con todo el contenido generado.
@Html.Action("Cabecera")

ActionLink(s: [descripción], s: [acción], s: [controlador])
Permite generar enlaces a acciones determinadas de un controlador. Por ejemplo si queremos poner un enlace a la acción Index del Home usaríamos
@Html.ActionLink("Página principal", "Index", "Home")

Se usa en las plantillas para realizar cualquier acción con el modelo del controlador, alta, baja, edición o borrado.
Dispone de muchas sobrecargas este Helper, permitiendo desde indicar protocolor, servidor y ancla, hasta definir los atributos HTML.
RouteLink(s: [descripción], d: [valores ruta])
Es parecido alterior, porque genera un enlace a una ruta o una acción de un controlador. Es algo más artesanal ya que no dispone de tantas sobrecargas y todos los valores se meten en un campo o se llama a la routa por su nombre (en caso que se haya definido previamente). Si queremos enlazar con la acción “Acerca de” usaríamos
@Html.RouteLink("Acerca de", new { controller = "Home", action="About"})

RenderAction(s: [nombre acción])
Es idéntica a Action con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador.

Si deseamos que una acción sea sólo llamada desde Action o RenderAction pero no directamente como una dirección más en el navegador podemos usar la anotación ChildActionOnly antes de la declaración
[ChildActionOnly]
  public ActionResult Cabecera() {
}

Helpers con el modelo

Lo normal no será generar todo el código HTML sino que se genere a partir de un modelo, por lo tanto MVC ofrece tdo lo que hemos visto anteriormente para trabajar con modelos.
Analizamos un par, pero será lo mismo para el resto de compontes.
Partimos de que la vista incluye referencia al modelo por ejemplo al Libro que contiene una propiedad título
@model Proyecto.Libro

Todos los Helpers se llaman como antes pero postponiendo For al nombre, es decir TextBox pasa a ser TextBoxFor o Label pasa a ser LabelFor. Luego para hacer referencia a una propiedad del modelo se usa un alias por ejemplo m => m.propiedad o l => l.propiedad (lo que os sea más cómodo)
Si necesitamos mostrar la etiqueta, la caja de texto y la validación
    @Html.LabelFor(l => l.Titulo)
    @Html.TextBoxFor(l => l.Titulo)
    @Html.ValidationMessageFor(l => l.Titulo)


Si lo ejecutamos lo primero que vemos es que el LabelFor de un modelo no es muy útil porque es el nombre del campo. Muchas veces siglas de un campo de la base de datos, o todo en mayúsculas.
MVC incluye data annotations en los campos lo que permite personalizar la información que luego se verá con estos Helpers. Por el momento sólo vamos a ver el de la descripción, pero mañana nos centraremos en todo el tema de validación, que es donde realmente se saca el potencial.
Las anotaciones se ponen con corchetes antes de la definición de la propiedad.
        [Display(Name = "Título del libro")]
        public string Titulo { get; set; }

Display permite personalizar aspectos de la visualización. Con Name le indicamos el nombre o label que tendrá esta propiedad. El resultado es:
Personalizar el nombre del campo

Personalizados

Como es lógico MVC nos permite crear nuestros propios Helpers para darle mayor potencia a éstos. Se comportan como una función a la que se pasan parámetros si los necesita, y dentro genera el código que queremos mostrar.
El formato es @Helper [Nombre Función]( [parámetros] )
@helper BreadCumb(string[] elementos) {
    <div>
    for(int i=0; i < elementos.Count(); i++)
    {
        <span>@elementos[i]</span>
   
        if (i < elementos.Count() - 1)
        {
            <text>></text>
        }  
    }
    </div>
}

Luego para llamarla @NombreFunción(parámetros)
@BreadCumb(new[] {"Inicio", "Administración", "Secciones"})

El resultado es el siguiente
Helper personalizado
Es costumbre a la hora de poner los parámetros, anteponer el nombre de cada parámetro y luego : (dos puntos).
@BreadCumb(elementos: new[] {"Inicio", "Administración", "Secciones"})

Si deseamos que es helper sea reutilizables desde cualquier vista, añadimos la carpeta App_Code a nuestro proyecto (no aparece como opción en las carpetas de ASP.NET) y creamos el fichero BreadCumbHelpers.cshtml. Copiamos el código del Helper y lo guardamos.
Ahora para hacerle referencia desde la vista llamaremos al helper de la siguiente manera
@[Nombre fichero (sin extensión)].[Nombre del helper o función]( [parámetros])
@BreadcumbHelpers.BreadCumb(elementos: new[] { "Inicio", "Administración", "Secciones" })


Aunque puede parecer que crear un Helper es lo mismo que llamar a vistas parciales (que vimos ayer) porque ambas se usan para reaprovechar código o dejarlo más estructurado, si que es cierto que cada uno tiene su uso.
Helpers personalizado está pensado para pequeños trozos de código, que generan una programación sencilla y que se comparte con diferentes vistas de tu proyecto o incluso entre varios.
Partial views están orientadas a secciones de código, con el objetivo de hacer más clara la estructura. Puede contener una programación tan complicada como la vista que les llama.
Disponemos de dos HTMLHelpers dedicados a trabajar con Partial views (ayer usamos el comando RenderPage).
Partial(s: nombrevista)
Genera una cadena de texto con la ejecución de la vista parcial
@Html.Partial("_Cabecera")

RenderPartial(s: nombrevista)
Es idéntica a Partial con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador.
@{ Html.RenderPartial("_Cabecera"); }

Personalizados con HtmlHelper

Hay otra forma de crear Helpers personalizados en el que “construimos el contenido” que queremos generar. Será una clase, con métodos que no generan HTML directamente como una vista parcial, si no que devuelve un objeto de tipo IHtmlString. Las etiquetas se generan con TagBuildery  los atributos se añaden con MergeAttribute.
Un ejemplo muy útil, y que encontramos en muchas páginas, es crear nuestro propio ActionLink para trabajar con imágenes.
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace _3_MVCHelpers.Helpers
{
    public static class HtmlImageActionLinkHelper
    {
        public static IHtmlString ImageActionLink(
            this HtmlHelper helper,
            string imageUrl,
            string actionName,
            object routeValues,
            object htmlAttributes
        )
        {
            var builder = new TagBuilder("img");
            builder.MergeAttribute("src", imageUrl);
            builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            var link = helper.ActionLink("[replaceme]", actionName, routeValues);
            var html = link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
            return new HtmlString(html);
        }
    }

}

Para referenciarlo en nuestra propia vista, usaremos @Html.[Método] ([Parámetros]).
Si queremos añadir una imagen 012.jpg que al pulsar sobre ella vaya a la acción Index usaríamos:
@Html.ImageActionLink(
    Url.Content("~/Fotos/012.jpg"),
    "Index",
    new { id = 5 },
    new { id = "imgnb", width = "100px", height = "150px", alt = "Foto playa de Alicante" }
)
Como ya se comentó el primer día las plantillas que usa el Visual Studio son personalizables. Se almacena por defecto en Visual Studio 2010 en la carpeta
C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\
CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML
Si accedemos veremos 6 ficheros con la extensión tt. Lo primero que podemos pensar es en modificarlo directamente, pero esto afecta a todos los proyectos. Además está en una carpeta que es muchas ocasiones, y por medidas de seguridad, nos pide permisos de administrador.
Lo mejor es coger la carpeta CodeTemplates y copiarla a la raíz de nuestra aplicación (podemos borrar todo lo que no sea AddView\CSHTML).

El siguiente paso para poder hacer modificaciones a la plantilla es cambiar el parámetro Herramienta personalizada a las 6 plantillas. Por defecto tiene el valor TextTemplatingFileGenerator. Lo dejamos vacío.


¿Podemos crear nuevas plantillas y que aparezcan al crear una vista?
Por supuesto. Vamos a duplicar la plantilla para a vista de listado (list.tt) para que no incluya ni dar de alta, ni poder gestionar los registros.
Aunque el código no es tan legible como en Razor, buscamos los bloques de HTML y vamos eliminando los contenidos que nos interesen. El objetivo es que se vea un listado con este formato.
Un problema que nos podemos encontrar es que en los listados personalizados el tipo de libro aparezca como código y no como descripción. Debemos realizar unos cambios.
En el controlador, hacer un include la tabla relacionada.
        public ActionResult Buscar(string palabra)
        {
            IEnumerable<CSI_LIBRO> libros;

            using (var bd = new EntitiesBiblioteca())
            {
                libros = bd.CSI_LIBRO.Include("CSI_TIPOLIBRO");

                if (!String.IsNullOrEmpty(palabra))
                {
                    libros = libros.Where(l => l.TITULO.ToUpper().Contains(palabra.ToUpper()));
                }

                libros = libros.ToList();
            }

            return View(libros);
        }

En la vista cambiar el campo que es clave ajena por la [tabla relación].[campo descripción] (sólo en el caso que hayamos realizado el include porque si no es inaccesible).
        <td>
            @Html.DisplayFor(modelItem => item.CSI_TIPOLIBRO.DESCRIPCION)
        </td>

ValidationSummary([Exclude property-level error])
Muestra una lista no ordenada de todos los errores que se producen al validar el formulario.
Se puede validar todo o excluir los errores a nivel de las propiedades del modelo
Los errores se pueden lanzar en tiempo de ejecución con la propiedad AddModelError(campo, mensaje de error)  del objeto ModelState.
En caso de que el campo lo dejemos vacío estamos lanzando un error a nivel de modelo
            ModelState.AddModelError("", "Prueba de un error general");

y si indicamos el campo lo hacemos a nivel de propiedad.
            ModelState.AddModelError("Nombre", "El nombre no cumple con los requisitos");

Añadimos a la vista un sumario de validación
@using(Html.BeginForm())
{
    @Html.ValidationSummary(false)
}

El resultado

Por defecto asigna el estilo "validation-summary-errors”.

ValidationMessage (s:[Nombre del campo], o:[Mensaje de error])
En caso de que no queramos que sea un sumario el que recoja todos los mensajes, si no que cada mensaje aparezca en el punto que indiquemos (normalmente a la derecha del campo), usaremos este Helper.
Si no se especifica el mensaje de error, todos aquellos errores que se produzcan (o provoquemos) se visualizarán en este punto.
    @Html.ValidationMessage("Nombre")

Se visualizará de la siguiente manera

Por defecto asigna el estilo “field-validation-error”.

Action(s: [nombre acción])
Nos permite llamar a un método / acción de un controlador. Puede parecerse mucho a las vistas parciales que vimos ayer y que recordaremos luego, pero lo cierto es que mientras las vistas parciales están pensadas para escribir bloques de código, action está orientada a ejecutar el proceso completo de la acción de un controlador (que incluye la generación del código con la Vista).
El resultado es una cadena de texto con todo el contenido generado.
@Html.Action("Cabecera")

ActionLink(s: [descripción], s: [acción], s: [controlador])
Permite generar enlaces a acciones determinadas de un controlador. Por ejemplo si queremos poner un enlace a la acción Index del Home usaríamos
@Html.ActionLink("Página principal", "Index", "Home")

Se usa en las plantillas para realizar cualquier acción con el modelo del controlador, alta, baja, edición o borrado.
Dispone de muchas sobrecargas este Helper, permitiendo desde indicar protocolor, servidor y ancla, hasta definir los atributos HTML.
RouteLink(s: [descripción], d: [valores ruta])
Es parecido alterior, porque genera un enlace a una ruta o una acción de un controlador. Es algo más artesanal ya que no dispone de tantas sobrecargas y todos los valores se meten en un campo o se llama a la routa por su nombre (en caso que se haya definido previamente). Si queremos enlazar con la acción “Acerca de” usaríamos
@Html.RouteLink("Acerca de", new { controller = "Home", action="About"})

RenderAction(s: [nombre acción])
Es idéntica a Action con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador.

Si deseamos que una acción sea sólo llamada desde Action o RenderAction pero no directamente como una dirección más en el navegador podemos usar la anotación ChildActionOnly antes de la declaración
[ChildActionOnly]
public ActionResult Cabecera() {
}

Helpers con el modelo

Lo normal no será generar todo el código HTML sino que se genere a partir de un modelo, por lo tanto MVC ofrece tdo lo que hemos visto anteriormente para trabajar con modelos.
Analizamos un par, pero será lo mismo para el resto de compontes.
Partimos de que la vista incluye referencia al modelo por ejemplo al Libro que contiene una propiedad título
@model Proyecto.Libro

Todos los Helpers se llaman como antes pero postponiendo For al nombre, es decir TextBox pasa a ser TextBoxFor o Label pasa a ser LabelFor. Luego para hacer referencia a una propiedad del modelo se usa un alias por ejemplo m => m.propiedad o l => l.propiedad (lo que os sea más cómodo)
Si necesitamos mostrar la etiqueta, la caja de texto y la validación
    @Html.LabelFor(l => l.Titulo)
    @Html.TextBoxFor(l => l.Titulo)
    @Html.ValidationMessageFor(l => l.Titulo)


Si lo ejecutamos lo primero que vemos es que el LabelFor de un modelo no es muy útil porque es el nombre del campo. Muchas veces siglas de un campo de la base de datos, o todo en mayúsculas.
MVC incluye data annotations en los campos lo que permite personalizar la información que luego se verá con estos Helpers. Por el momento sólo vamos a ver el de la descripción, pero mañana nos centraremos en todo el tema de validación, que es donde realmente se saca el potencial.
Las anotaciones se ponen con corchetes antes de la definición de la propiedad.
        [Display(Name = "Título del libro")]
        public string Titulo { get; set; }

Display permite personalizar aspectos de la visualización. Con Name le indicamos el nombre o label que tendrá esta propiedad. El resultado es:



Personalizados

Como es lógico MVC nos permite crear nuestros propios Helpers para darle mayor potencia a éstos. Se comportan como una función a la que se pasan parámetros si los necesita, y dentro genera el código que queremos mostrar.
El formato es @Helper [Nombre Función]( [parámetros] )
@helper BreadCumb(string[] elementos) {
    <div>
    for(int i=0; i < elementos.Count(); i++)
    {
        <span>@elementos[i]</span>
   
        if (i < elementos.Count() - 1)
        {
            <text>></text>
        }  
    }
    </div>
}

Luego para llamarla @NombreFunción(parámetros)
@BreadCumb(new[] {"Inicio", "Administración", "Secciones"})

El resultado es el siguiente

Es costumbre a la hora de poner los parámetros, anteponer el nombre de cada parámetro y luego : (dos puntos).
@BreadCumb(elementos: new[] {"Inicio", "Administración", "Secciones"})

Si deseamos que es helper sea reutilizables desde cualquier vista, añadimos la carpeta App_Code a nuestro proyecto (no aparece como opción en las carpetas de ASP.NET) y creamos el fichero BreadCumbHelpers.cshtml. Copiamos el código del Helper y lo guardamos.
Ahora para hacerle referencia desde la vista llamaremos al helper de la siguiente manera
@[Nombre fichero (sin extensión)].[Nombre del helper o función]( [parámetros])
@BreadcumbHelpers.BreadCumb(elementos: new[] { "Inicio", "Administración", "Secciones" })


Aunque puede parecer que crear un Helper es lo mismo que llamar a vistas parciales (que vimos ayer) porque ambas se usan para reaprovechar código o dejarlo más estructurado, si que es cierto que cada uno tiene su uso.
Helpers personalizado está pensado para pequeños trozos de código, que generan una programación sencilla y que se comparte con diferentes vistas de tu proyecto o incluso entre varios.
Partial views están orientadas a secciones de código, con el objetivo de hacer más clara la estructura. Puede contener una programación tan complicada como la vista que les llama.

Disponemos de dos HTMLHelpers dedicados a trabajar con Partial views (ayer usamos el comando RenderPage).
Partial(s: nombrevista)
Genera una cadena de texto con la ejecución de la vista parcial
@Html.Partial("_Cabecera")

RenderPartial(s: nombrevista)
Es idéntica a Partial con la diferencia de que no almacena el contenido en una cadena de texto si no que la escribe directamente al objeto Response, con lo que se visualiza por el navegador.
@{ Html.RenderPartial("_Cabecera"); }

Personalizados con HtmlHelper

Hay otra forma de crear Helpers personalizados en el que “construimos el contenido” que queremos generar. Será una clase, con métodos que no generan HTML directamente como una vista parcial, si no que devuelve un objeto de tipo IHtmlString. Las etiquetas se generan con TagBuildery  los atributos se añaden con MergeAttribute.
Un ejemplo muy útil, y que encontramos en muchas páginas, es crear nuestro propio ActionLink para trabajar con imágenes.
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace _3_MVCHelpers.Helpers
{
    public static class HtmlImageActionLinkHelper
    {
        public static IHtmlString ImageActionLink(
            this HtmlHelper helper,
            string imageUrl,
            string actionName,
            object routeValues,
            object htmlAttributes
        )
        {
            var builder = new TagBuilder("img");
            builder.MergeAttribute("src", imageUrl);
            builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            var link = helper.ActionLink("[replaceme]", actionName, routeValues);
            var html = link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
            return new HtmlString(html);
        }
    }

}

Para referenciarlo en nuestra propia vista, usaremos @Html.[Método] ([Parámetros]).
Si queremos añadir una imagen 012.jpg que al pulsar sobre ella vaya a la acción Index usaríamos:
@Html.ImageActionLink(
    Url.Content("~/Fotos/012.jpg"),
    "Index",
    new { id = 5 },
    new { id = "imgnb", width = "100px", height = "150px", alt = "Foto playa de Alicante" }
)

Comentarios

Entradas populares