Twitter Feed Popout byInfofru

OverrideThis.com

Adventures in .NET Software Craftsmanship!

Learning Razor–Writing a Once Extension

So, I have been –officially- using Razor since the ASP.NET MVC 3.0 release and I am loving every minute of it, but from time to time I still find myself looking back at Spark features that I wish I had in Razor. One feature that had been staring at me in the face for a while was the use of the ONCE attribute in spark.  The ONCE attribute allows you to specify with a very elegant syntax that something should just be rendered once no matter how many times is specified. (See code snippet below, or whole documentation here at the Spark website).

 

<!--  add jquery if it hasn't been added yet -->
<script once="jquery-1.5.1" 
  type="text/javascript" 
  src="~/content/js/jquery-1.5.1.js"/>

 

There are probably a couple of ways you could make this work in Razor but the easiest way to go about it is to write a Templated Html Helper (More info on the topic by Phil Haack here, and Andrew Nurse here).  Building a Templated HTML Helper allow us to use a syntax almost as sweet as Spark’s (see snippet below).

 

<!--  add jquery if it hasn't been added yet -->
@Html.Once("jquery-1.5.1",
    @<script src='@Url.Content("~/Scripts/jquery-1.5.1.js")' type="text/javascript"></script>
)

 

While the source code is extremely simple it does rely on the use of the HttpContext to establish uniqueness, this implies that it will safely work across Partial and Child Action calls but will not work across AJAX requests.

 

using System;
using System.Web.Mvc;
using System.Web.WebPages;

public static class HtmlHelperExtensions
{
    private static string RAZOR_ONCE_EXTENSION_KEY_FORMAT 
        = "RAZOR_ONCE_EXTENSION_KEY_{0}";

    public static HelperResult Once(this HtmlHelper helper, 
        string onceKey, 
        Func<object, HelperResult> template)
    {
        var contextKey = string
            .Format(RAZOR_ONCE_EXTENSION_KEY_FORMAT, onceKey);
        return new HelperResult(writer =>
        {
            if (helper.ViewContext.HttpContext.Items[contextKey] == null)
                template(null).WriteTo(writer);
            helper.ViewContext.HttpContext.Items[contextKey] = "OUTPUT_RENDERED";
        });
    }
}