Consolidando las respuestas de Jamiec y Martin Booth. Creé el siguiente método de extensión. Se necesita un IEnumerable como primer argumento, y luego dos delegados para representar el texto. En las Vistas Razor podemos pasar Delegados Templados dos de estos parámetros. En resumen, esto significa que puede entregar plantillas. Así que aquí es el método de extensión y cómo se le puede llamar:
public static HelperResult Each<TItem>(this IEnumerable<TItem> items,
Func<TItem, HelperResult> eachTemplate,
Func<dynamic, HelperResult> other)
{
return new HelperResult(writer =>
{
foreach (var item in items)
{
var result = eachTemplate(item);
result.WriteTo(writer);
}
if (!items.Any())
{
var otherResult = other(new ExpandoObject());
// var otherResult = other(default(TItem));
otherResult.WriteTo(writer);
}
});
}
Y en las vistas Razor:
@Model.Users.Each(
@<li>@item.Name</li>,
@<li>
<b>No Items</b>
</li>
)
Con todo, bastante limpio.
ACTUALIZACIÓN implementando las sugerencias hechas en los comentarios. Este método de extensión toma un argumento para recorrer los elementos en la colección y devuelve un HelperResult personalizado. En ese resultado, uno puede llamar al método Else
para pasar un delegado de plantilla en caso de que no se encuentren ítems.
public static class HtmlHelpers
{
public static ElseHelperResult<TItem> Each<TItem>(this IEnumerable<TItem> items,
Func<TItem, HelperResult> eachTemplate)
{
return ElseHelperResult<TItem>.Create(items, eachTemplate);
}
}
public class ElseHelperResult<T> : HelperResult
{
private class Data
{
public IEnumerable<T> Items { get; set; }
public Func<T, HelperResult> EachTemplate { get; set; }
public Func<dynamic, HelperResult> ElseTemplate { get; set; }
public Data(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
{
Items = items;
EachTemplate = eachTemplate;
}
public void Render(TextWriter writer)
{
foreach (var item in Items)
{
var result = EachTemplate(item);
result.WriteTo(writer);
}
if (!Items.Any() && ElseTemplate != null)
{
var otherResult = ElseTemplate(new ExpandoObject());
// var otherResult = other(default(TItem));
otherResult.WriteTo(writer);
}
}
}
public ElseHelperResult<T> Else(Func<dynamic, HelperResult> elseTemplate)
{
RenderingData.ElseTemplate = elseTemplate;
return this;
}
public static ElseHelperResult<T> Create(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
{
var data = new Data(items, eachTemplate);
return new ElseHelperResult<T>(data);
}
private ElseHelperResult(Data data)
: base(data.Render)
{
RenderingData = data;
}
private Data RenderingData { get; set; }
}
Esto entonces se puede llamar de la siguiente manera:
@(Model.Users
.Each(@<li>@item.Name</li>)
.Else(
@<li>
<b>No Users</b>
</li>
)
)
La coma es casi invisible. : P –
Estoy de acuerdo, pero no hay una manera real en que podamos eludir eso, ¿podemos? :) – Thomas
Si usa un parámetro con nombre para la cláusula else es probable que sea aún más legible y puede hacerlo opcional –