2011-10-19 7 views
8

Wiele silników szablonowych ma specjalny rodzaj składni, który jest kombinacją foreach i. Zasadniczo klauzula else jest wykonywana, gdy pętla foreach nie ma żadnych iteracji. Może to być przydatne, jeśli chcesz wyświetlić jakiś rodzaj żadnych elementów na liście zastępczej.Elegancki foreach - inaczej skonstruuj w Razor

W Twig na przykład for loop może wyglądać następująco

{% for user in users %} 
    <li>{{ user.username|e }}</li> 
{% else %} 
    <li><em>no user found</em></li> 
{% endfor %} 

Używając maszynki Zobacz Engine, szablon chcieliby jak ten, obejmujący dodatkowy czek od liczby elementów w kolekcji:

@foreach (var user in users) { 
    <li>@user.UserName</li> 
} 
@if (!users.Any()) { 
    <li><em>no user found</em></li> 
} 

Moje pytania brzmią: czy możemy osiągnąć podobną elegancję w taki czy inny sposób, używając mechanizmu Razor View Engine.

Odpowiedz

9

Konsolidacja odpowiedzi Jamieca i Martina Bootha. Stworzyłem następującą metodę rozszerzenia. Wymaga IEnumerable jako pierwszego argumentu, a następnie dwóch delegatów do renderowania tekstu. W widokach maszynki możemy podać Templated Delegates dwa te parametry. W skrócie oznacza to, że możesz podać szablony. Więc tutaj jest metoda rozszerzenie i jak można to nazwać:

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

I w widokach Razor:

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

W sumie, dość czyste.

UPDATE wdrażanie sugestii zawartych w komentarzach. Ta metoda rozszerzeń pobiera jeden argument, aby zapętlić elementy w kolekcji i zwraca niestandardowy HelperResult. Na tym przykładzie pomocy można wywołać metodę Else, aby przekazać w szablonie delegata na wypadek, gdyby nie znaleziono żadnych elementów.

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

Można wtedy nazywa się następująco:

@(Model.Users 
    .Each(@<li>@item.Name</li>) 
    .Else(
     @<li> 
      <b>No Users</b> 
     </li> 
     ) 
) 
+0

Przecinek jest prawie niewidoczny. : P –

+0

Zgadzam się, ale nie ma sposobu, w jaki możemy to obejść, prawda? :) – Thomas

+1

Jeśli użyjesz nazwanego parametru dla klauzuli else, prawdopodobnie jest on jeszcze bardziej czytelny i możesz uczynić go opcjonalnym. –

2

Jedynym sposobem mogę myśleć, aby osiągnąć coś takiego jest z kilkoma rozszerzeniami do 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; 
    } 
} 

Pozwala to na łańcuchu 2. zwraca na siebie jak demonstarted o tym żywym przykład: http://rextester.com/runcode?code=AEBQ75190 który wykorzystuje następujący kod:

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

Zupełnie jak by to dopasować się brzytwą szablonu im wciąż niepewny .... ale może to daje coś, aby przejść dalej.

0

Może to nie było możliwe, gdy pytanie zostało postawione, ale ja po prostu osiągnąć to tak:

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