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:
- Una cabecera: template Razor que se renderizará una sola vez al principio.
- Un cuerpo: template Razor que se renderizará una vez por cada elemento
- Un pie: template Razor que se renderizará una sola vez al final.
- 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:
- header, cuyo parámetro @item es dynamic.
- body, cuyo parámetro @item es TItem, donde TItem es el tipo del enumerable que se le pasa al helper
- 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:
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:
Y listos! Espero que esos dos posts sobre templates Razor os hayan parecido interesantes!
Un saludo a todos! 😉
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) =
y claro al pasar los parametros al repeater no soy capaz…
Gracias!
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! 😉
En esta pagina se pueden ver las equivalencias entre la sintaxis de razon en C# Y VB
http://www.variablenotfound.com/2011/05/sintaxis-razor-con-vbnet.html