ASP.NET MVC3: Un helper Repeater

Muy buenas! En el post anterior comenté la característica de los templates de Razor y hoy vamos a ver como podríamos crear un helper que emule un poco el control Repeater que hay en webforms (salvando las distancias, claro).

Vamos a crear un helper externo, es decir que sea reutilizable en distintos proyectos: para ello nuestro helper va a residir en una clase (en mi ejemplo en el propio proyecto web, pero se podría situar en una librería de clases para ser reutilizable).

Esqueleto inicial

A nuestro helper se le pasará lo siguiente:

  1. Una cabecera: template Razor que se renderizará una sola vez al principio.
  2. Un cuerpo: template Razor que se renderizará una vez por cada elemento
  3. Un pie: template Razor que se renderizará una sola vez al final.
  4. Una colección de elementos (por cada elemento se renderizará el cuerpo).

Recordáis que ayer comentamos que los templates Razor son Func<T, HelperResult>?

Bien, pues vamos a declarar nuestro helper:

 

public static IHtmlString Repeat<TItem>(
IEnumerable<TItem> items,
Func<dynamic, HelperResult> header,
Func<TItem, HelperResult> body,
Func<dynamic, HelperResult> footer
)
{
var builder = new StringBuilder();
if (header != null)
{
builder.Append(header(null).ToHtmlString());
}

foreach (var item in items)
{
builder.Append(body(item).ToHtmlString());
}
if (footer != null)
{
builder.Append(footer(null).ToHtmlString());
}

return MvcHtmlString.Create(builder.ToString());
}

Los templates siempre están declarados como Func<T, HelperResult>, en este caso declaramos tres templates:

  1. header, cuyo parámetro @item es dynamic.
  2. body, cuyo parámetro @item es TItem, donde TItem es el tipo del enumerable que se le pasa al helper
  3. footer, cuyo parámetro @item es dynamic

Para invocar los templates y obtener el html asociado simplemente invocamos el Func (pasándole el parámetro del tipo correcto) y sobre el resultado llamamos a ToHtmlString: Este método nos devuelve la cadena HTML que ha parseado Razor.

Ahora p.ej. me creo una acción tal en el controlador:

public ActionResult List()
{
return View(new List<Product>
{
new Product() {Nombre="PS3", Precio=300},
new Product() {Nombre="XBox360", Precio=150},
new Product() {Nombre="Wii", Precio=100}
});
}

Y en la vista asociada:

@using MvcApplication14.Helpers
@model IEnumerable<MvcApplication14.Models.Product>

<table>
@Repeater.Repeat(Model,
@<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>,
@<tr><td>@item.Nombre</td><td>@item.Precio</td></tr>,
null)
</table>

Fijaos en la llamada a Repeater.Repeat, donde le paso el modelo que recibe la vista (que es un IEnumerable de Product) y los tres templates Razor (en este caso no le paso footer y por ello pongo null).

En el segundo template (body) puedo acceder al elemento que se está renderizando mediante @item. Además, dado que en helper he declarado el template body de tipo Func<TItem, HelperResult>, tengo soporte de Intellisense:

image

Si teclease @item en el template header o footer no tendría intellisense porque los he declarado dynamic en el helper (además ojo, que en el helper le paso null, por lo que rebentaría si usamos @item en el template header o footer).

Y listos! El código HTML generado es:

<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>
<tr><td>PS3</td><td>300</td></tr>
<tr><td>XBox360</td><td>150</td></tr>
<tr><td>Wii</td><td>100</td></tr>

Guay, no?

Podríamos “complicar” un poco el helper, para que desde los templates supiesemos si estamos en una fila par o impar y así aplicar clases… Para ello nos basta con declarar una clase adicional en nuestor helper:

public class RepeaterItem<TItem>
{
public int Index { get; private set; }
public bool IsEven { get { return Index % 2 == 0; } }
public TItem Item { get; private set; }

public RepeaterItem(TItem item, int index)
{
Index = index;
Item = item;
}
}

Esa clase simplemente contiene el índice del elemento actual, el propio elemento y una propiedad que indica si es par o no.

Ahora modificamos el helper para pasarle al template body un objeto RepeaterItem<TItem>:

public static class Repeater
{
public static IHtmlString Repeat<TItem>(
IEnumerable<TItem> items,
Func<dynamic, HelperResult> header,
Func<RepeaterItem<TItem>, HelperResult> body,
Func<dynamic, HelperResult> footer
)
{
var builder = new StringBuilder();
if (header != null)
{
builder.Append(header(null).ToHtmlString());
}

var count = 0;
foreach (var item in items)
{
var repeaterItem = new RepeaterItem<TItem>(item, count++);
builder.Append(body(repeaterItem).ToHtmlString());
}
if (footer != null)
{
builder.Append(footer(null).ToHtmlString());
}

return MvcHtmlString.Create(builder.ToString());
}
}

Los cambios básicamente son declarar el template domo Func<RepeaterItem<Titem>> y cuando invocamos el template, crear antes un objeto RepeaterItem y pasárselo como parámetro.

Finalmente ahora debemos modificar la vista, ya que el parámetro @item de nuestro template es ahora un RepeaterItem<TItem>:

@Repeater.Repeat(Model, 
@<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>,
@<tr style="@(item.IsEven ? "background-color: white" : "background-color:pink")">
<td>@item.Item.Nombre</td><td>@item.Item.Precio</td></tr>,
null)

Fijaos como dentro del template body puedo preguntar si el elemento actual es par (@item.IsEven y acceder al propio elemento @item.Item). En este caso uso @item.IsEven para cambiar el color de fondo de la fila:

image

Y listos! Espero que esos dos posts sobre templates Razor os hayan parecido interesantes!

Un saludo a todos! 😉

3 comentarios sobre “ASP.NET MVC3: Un helper Repeater”

  1. Hola me estoy volviendo loco para hacerlo funcionar en VB.

    ¿Sabrías Como se definiría una plantilla razor del tipo que defines en tu anterior post?

    Func h =
    @

    Esto es un template al que se le han pasado los datos: @item

    Intento esto y no compila:
    Dim h As Func(Of String, HelperResult) =

    @item

    y claro al pasar los parametros al repeater no soy capaz…
    Gracias!

  2. Buenas Fernando!
    Pues no lo sé, la verdad es que Razor en sintaxis VB nunca lo he usado…

    A ver si alguien sabe como puede usarse (o si puede usarse en VB porque a veces hay pequeñas diferencias entre el parser de Razor de C# y el de VB).

    Saludos y gracias por comentar! 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *