2011-10-19 16 views
8

很多模板引擎都有一种特殊的语法,它是foreachelse的组合。当foreach循环没有任何迭代时,基本上会执行else子句。如果您想在列表后备中显示某种没有项目,这可能很有用。优雅的foreach - 其他构造在剃刀

Twig例如,for loop可以这个样子

{% 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 View Engine以某种方式实现类似的优雅。

回答

9

巩固Jamiec和马丁展位的答案。我创建了以下扩展方法。它将IEnumerable作为第一个参数,然后使用两个代表来呈现文本。在Razor Views中我们可以通过模板化代表两个这些参数。总之这意味着你可以在模板中输入。因此,这里的扩展方法,你可以怎么称呼它:

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); 
      } 
     }); 
    } 

而且在剃刀观点:

@Model.Users.Each(
    @<li>@item.Name</li>, 
    @<li> 
     <b>No Items</b> 
    </li> 
) 

所有的一切,很干净。

UPDATE实施评论中提出的建议。此扩展方法需要一个参数来循环集合中的项目并返回一个自定义的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() && 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> 
     ) 
) 
+0

逗号几乎不可见。 :P –

+0

我同意,但我们没有办法绕过这个方法,我们可以吗? :) – Thomas

+1

如果你为else子句使用命名参数,它可能更具可读性,你可以使它成为可选 –

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个呼叫到对方通过这个活生生的例子demonstarted: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")); 

相当如何做到这一点适合用剃刀模板IM仍然不确定....但也许这给你的东西去。

0

也许这是不可能的,当问题被提出,但我只是实现了这个是这样的:

@if (Model.EmailAddress.Count() > 0) 
{ 
    foreach (var emailAddress in Model.EmailAddress) 
    { 
     <div>@emailAddress.EmailAddress</div> 
    } 
} else { <span>No email addresses to display</span> }