#.net #asp.net-mvc #razor #templating #twig
#.net #asp.net-mvc #razor #создание шаблонов #веточка
Вопрос:
Многие механизмы создания шаблонов имеют особый синтаксис, представляющий собой комбинацию foreach
и else
. В основном else
предложение выполняется, когда в foreach
цикле нет итераций. Это может быть полезно, если вы хотите отобразить какой-то резервный вариант отсутствия элементов в списке.
Например, в Twig for
цикл может выглядеть так
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no user found</em></li>
{% endfor %}
Используя механизм просмотра Razor, шаблон должен выглядеть следующим образом, включая дополнительную проверку количества элементов в коллекции:
@foreach (var user in users) {
<li>@user.UserName</li>
}
@if (!users.Any()) {
<li><em>no user found</em></li>
}
Итак, мои вопросы: можем ли мы достичь подобной элегантности тем или иным способом, используя механизм просмотра Razor.
Ответ №1:
Консолидация ответов Джейми и Мартина Бута. Я создал следующий метод расширения. Он принимает IEnumerable в качестве первого аргумента, а затем два делегата для отображения текста. В представлениях Razor мы можем передать шаблонным делегатам два этих параметра. Короче говоря, это означает, что вы можете вводить шаблоны. Итак, вот метод расширения и как вы можете его вызвать:
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);
}
});
}
И в представлениях Razor:
@Model.Users.Each(
@<li>@item.Name</li>,
@<li>
<b>No Items</b>
</li>
)
В целом, довольно чисто.
ОБНОВИТЕ реализацию предложений, сделанных в комментариях. Этот метод расширения принимает один аргумент для перебора элементов в коллекции и возвращает пользовательский HelperResult . В этом helperresult можно вызвать Else
метод для передачи делегата шаблона в случае, если элементы не найдены.
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() amp;amp; 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; }
}
Затем это можно вызвать следующим образом:
@(Model.Users
.Each(@<li>@item.Name</li>)
.Else(
@<li>
<b>No Users</b>
</li>
)
)
Комментарии:
1. Запятая почти невидима. 😛
2. Я согласен, но нет никакого реального способа обойти это, не так ли? 🙂
3. Если вы используете именованный параметр для предложения else, он, вероятно, будет еще более читаемым, и вы можете сделать его необязательным
4. Я думаю, вы могли бы получить либо из
HelperResult
, либо изIHtmlString
и создать.Else()
метод аналогичным образом. Итак, вы бы написали:@Models.Users.Each(@<li>@item.name</li>).Else(@<li><b>No items</b></li>)
5. Еще лучше, верните пользовательский класс из метода for each extention, который реализует ihtmlstring, но это означает, что вы можете заставить метод else применяться только к этому пользовательскому классу (т.Е. Не к любому ienumerable)
Ответ №2:
Единственный способ, которым я мог подумать, добиться чего-то подобного, — это использовать пару расширений для IEnumerable<T>
:
public static class IEnumerableExtensions
{
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
foreach(T item in enumerable)
action(item);
return enumerable;
}
public static IEnumerable<T> WhenEmpty<T>(this IEnumerable<T> enumerable, Action action)
{
if(!enumerable.Any())
action();
return enumerable;
}
}
Это позволяет вам связывать 2 вызова друг с другом, как показано в этом живом примере: http://rextester.com/runcode?code=AEBQ75190 который использует следующий код:
var listWithItems = new int[] {1,2,3};
var emptyList = new int[]{};
listWithItems.ForEach(i => Console.WriteLine(i))
.WhenEmpty( () => Console.WriteLine("This should never display"));
emptyList.ForEach(i => Console.WriteLine(i))
.WhenEmpty( () => Console.WriteLine("This list was empty"));
Как это будет вписываться в шаблон Razor, в котором я все еще не уверен …. но, возможно, это даст вам что-то для продолжения.
Ответ №3:
В afaik ничего не построено, но вы, вероятно, могли бы расширить это в соответствии с вашими потребностями:
http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx
Возможно, я смогу помочь позже, когда не буду пользоваться своим телефоном, если у вас все еще нет ответа
Комментарии:
1. Я думаю, что если вы объедините это с ответом Джейми, у вас будет решение!
Ответ №4:
Возможно, это было невозможно, когда был задан вопрос, но я только что добился этого следующим образом:
@if (Model.EmailAddress.Count() > 0)
{
foreach (var emailAddress in Model.EmailAddress)
{
<div>@emailAddress.EmailAddress</div>
}
} else { <span>No email addresses to display</span> }