2011-10-19 8 views
8

Molti motori di modelli hanno un tipo speciale di sintassi che è una combinazione di foreach e else. Fondamentalmente la clausola else viene eseguita quando il ciclo foreach non ha iterazioni. Questo può essere utile se si desidera visualizzare una sorta di nessun elemento nell'elenco di riserva.Elegant foreach - else construct in Razor

In Twig ad esempio, il for loop può assomigliare a questo

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

Usando il rasoio View Engine, il modello vorrebbe in questo modo, per un ulteriore controllo sul numero di elementi della collezione:

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

Quindi le mie domande sono: possiamo ottenere un'eleganza simile in un modo o nell'altro utilizzando il Razor View Engine.

risposta

9

Consolidare le risposte di Jamiec e Martin Booth. Ho creato il seguente metodo di estensione. Prende un oggetto IEnumerable come primo argomento e quindi due delegati per il rendering del testo. Nelle Visualizzazioni del rasoio possiamo passare a Delegati templati due questi parametri. In breve, questo significa che puoi dare dei modelli. Così qui è il metodo di estensione e come si può chiamare:

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

E il punto di vista Razor:

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

Tutto sommato, abbastanza pulito.

UPDATE implementazione dei suggerimenti inseriti nei commenti. Questo metodo di estensione accetta un argomento per eseguire il loop sugli elementi nella raccolta e restituisce un HelperResult personalizzato. In questo helperresult, è possibile chiamare il metodo Else per passare un delegato del modello nel caso in cui non vengano trovati elementi.

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

Questo può quindi essere chiamato come segue:

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

La virgola è quasi invisibile. : P –

+0

Sono d'accordo, ma non esiste un vero modo per aggirare questo problema, possiamo? :) – Thomas

+1

Se si utilizza un parametro denominato per la clausola else, è probabilmente ancora più leggibile e si può renderlo opzionale –

2

L'unico modo che ho potuto pensare di realizzare qualcosa di simile è con un paio di estensioni per 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; 
    } 
} 

Ciò consente di catena 2 chiamate su ogni altro come demonstarted da questo esempio, dal vivo: http://rextester.com/runcode?code=AEBQ75190 che utilizza il seguente codice:

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

Piuttosto come questo sarebbe in sintonia con un rasoio modello im ancora incerto .... ma forse questo ti dà qualcosa per andare avanti.

1

Nulla costruito nel afaik, ma probabilmente si potrebbe estendere questo in base alle proprie esigenze:

http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx

potrei essere in grado di aiutare più tardi, quando non sto usando il mio telefono, se ancora non si avere una risposta

+1

Penso che se combini questo con la risposta di jamiec avrai una soluzione! –

0

Forse questo non era possibile quando la domanda è stata posta, ma ho appena raggiunto questo in questo modo:

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