Many people write ForEach
extension methods for MVC WebForms views, which take a sequence and a delegate to turn each item in the sequence into HTML.
For example:
public static void ForEach<T>(this HtmlHelper html, IEnumerable<T> set, Action<T> htmlWriter) { foreach (var item in set) { htmlWriter(item); } }
(The unused html parameter allows it to be called as an extension method like other HTML helpers)
This code can be called like this:
<ul> <% Html.ForEach( Enumerable.Range(1, 10), item => { %> <li><%= item %></li> <% } ); %> </ul>
This code creates a lambda expression that writes markup to the page’s response stream by ending the code block inside the lambda body. Neither the lambda expression nor the ForEach
helper itself return anything; they both write directly to the response.
The List<T>.ForEach
method can be called exactly the same way.
In Razor views, this method cannot easily be called directly, since Razor pages cannot put markup inside of expressions. You can use a workaround by creating an inline helper and calling it immediately, but it would be better to rewrite the ForEach
method to take an inline helper directly.
The naïve way to do that is like this:
public static IHtmlString ForEachSimple<T>( this HtmlHelper html, IEnumerable<T> set, Func<T, HelperResult> htmlCreator ) { return new HtmlString(String.Concat(set.Select(htmlCreator))); }
The htmlCreator
delegate, which can be passed as an inline helper, returns a HelperResult
object containing the markup generated for an item.
This code uses LINQ to call htmlCreator
on each item in the set (the Select
call), then calls String.Concat
to combine them all into one giant string. (String.Concat
will call ToString
on each HelperResult
, which will return the generated markup) We could also call String.Join
to put a separator, such as a newline, between every two items.
Finally, it returns an HtmlString to prevent Razor from escaping the returned HTML.
It’s equivalent to the following code using a StringBuilder
(this is what String.Concat
does internally)
var builder = new StringBuilder(); foreach (var item in set) { HelperResult result = htmlCreator(item); builder.Append(result.ToString()); } return new HtmlString(builder.ToString());
This method can be called like this:
<ul> @Html.ForEachSimple( Enumerable.Range(1, 10), @<li>@item</li> ); </ul>
The problem with this approach is that it combines all of the content into a giant string. If there are a large number of items, or if each item will have a large amount of markup, this can become (a little bit) slow. It would be better to write each item directly to the caller’s response stream, without assembling any giant strings. This is where HelperResult
comes in.
The HelperResult class allows its caller to pass a TextWriter
to the WriteTo
method, and the helper delegate will write directly to this TextWriter
. I can take advantage of this to write a ForEach
extension that doesn’t build any strings, by returning a HelperResult
instead of a regular IHtmlString
.
public static HelperResult ForEachFast<T>( this HtmlHelper html, IEnumerable<T> set, Func<T, HelperResult> htmlCreator ) { return new HelperResult(tw => { foreach (var item in set) { htmlCreator(item).WriteTo(tw); } }); }This version creates a
HelperResult
with a delegate that writes each of its items in turn to the TextWriter
.