Meet my new HtmlHelper extensions friends, ForEach and If
Posted by Kenny Eliasson | Posted in C# , Extension Methods , Html , MVC | Posted on 13:15
How many times have you written code like this in your MVC views?
<%
int currentIndex = 0;
foreach(var item in Model.Items) {
%>
<% if(currentIndex == 0) {%>
<div class="item first"><%= item.Name %></div>
<% } else if(currentIndex == Items.Count()-1) { %>
<div class="item last"><%= currentIndex + 1 %> <%= item.Name %></div>
<% } else { %>
<div class="item"><%= item.Name %></div>
<% } %>
<%
currentIndex++;
}
%>
This is pure ugliness :(
What I missed was something like django for variables. They include both a Last and First variable on the loop.
After reading Phil Haacked's blog about "A code based repeater for .NET MVC" I decided to shamelessy take some parts of his code and write my own extension methods.
The first extension method I wrote was the Html.ForEach which gets me a index counter.
public static void ForEach<T>(this HtmlHelper html, IEnumerable<T> items, Action<T, int> render)
{
if (items == null)
return;
int i = 0;
items.ForEach(item => render(item, i++));
}
And to use it
<% Html.ForEach(Model.Items, (item, index) => { %>
<div>#<%= index + 1 %> <%= item.Name %>
<% }); %>
Pretty slick and I dont need to have the counter variable in the view code. It uses lambda in way I didn't thought was possible before I read Haacked blog post.
I then decided to see if I could manage to check if I was on the first or last item in the loop.
I came up with this
public static void ForEachExtra<T>(this HtmlHelper html, IEnumerable<T> items, Action<T, ForLoop<T>> render)
{
if (items == null)
return;
var loop = new ForLoop<T>(items);
int i = 0;
items.ForEach(item => { render(item, loop.Update(i)); i++; });
}
public class ForLoop<T>
{
private readonly int _itemCount;
public ForLoop(IEnumerable<T> items)
{
_itemCount = items.Count();
}
public int Counter { get; set; }
public int Counter0 { get; set; }
public bool First { get; set; }
public bool Last { get; set; }
public ForLoop<T> Update(int i)
{
Counter = i + 1;
Counter0 = i;
First = i == 0;
Last = i == _itemCount-1;
return this;
}
}
And to use this you will write
<% Html.ForEachExtra(Model.Templates, (template, loop) => {%>
<% if(loop.First) { %> First! <% }%>">
<div>#<%= loop.Counter %> - <%= template.Name %></div>
<% if(loop.Last) { %> Last! <% }%>
<% }); %>
Starting to look impressive here! :) But I still think I can get it better, so onto my other new extension method, If()!
What I want is to have code that looks something like this
<% Html.ForEachExtra(Model.Items, (item, loop) => { %>
<div class="item <%= Html.If(loop.First).Write("first").ElseIf(loop.Last).Write("last") %>
#<%=loop.counter%> <%= item.Name %>
</div>
<% }); %>
That would be awesome so off I went to write it.
public interface IConditionWrite
{
IElseIfConditionBuilder Write(string output);
}
public interface IElseIfConditionBuilder
{
IConditionWrite ElseIf(bool condition);
}
public class ConditionBuilder : IConditionWrite, IElseIfConditionBuilder
{
private readonly IList<FluentHtmlCondition> _conditions;
private FluentHtmlCondition _lastAddedCondition;
public ConditionBuilder()
{
_conditions = new List<FluentHtmlCondition>();
}
public IConditionWrite AddCondition(FluentHtmlCondition condition)
{
_lastAddedCondition = condition;
_conditions.Add(condition);
return this;
}
public override string ToString()
{
foreach (var condition in _conditions) {
if (condition.Fulfilled)
return condition.Output;
}
return "";
}
public IConditionWrite ElseIf(bool condition)
{
AddCondition(new FluentHtmlCondition(condition));
return this;
}
public IElseIfConditionBuilder Write(string output)
{
_lastAddedCondition.Output = output;
return this;
}
}
public class FluentHtmlCondition
{
public readonly bool Fulfilled;
public string Output;
public FluentHtmlCondition(bool fulfilled)
{
Fulfilled = fulfilled;
}
public void Write(string output)
{
Output = output;
}
}
And in my Html-helper extension class i add
public static IConditionWrite If(this HtmlHelper html, bool condition)
{
return new ConditionBuilder().AddCondition(new FluentHtmlCondition(condition));
}
And there you have it all, hope someone can have some use with it.