Here’s an ASP.NET MVC HTML Helper that helps in the following scenario. Let’s say you have a partial that can be included a number of times in a view but has a bit of common code that need only be included once. Typically that bit of common code would be some script.
In ASP.NET such a scenario is addressed by RegisterClientScriptBlock. In the Spark View Engine it’s taken care of by the once attribute.
So, inspired by Phil Haack’s Templated Razor Delegates post from earlier this year I knew you could write helper functions for Razor that could take arbitrary markup. The key to it is taking a Func
argument that returns a HelperResult
.
To use it you invoke it like so:
@Html.Once("some unique key", @<div>arbitrary markup that gets rendered just once</div>)
More typically, for including script, e.g.:
@Html.Once("TABLE_SORTER_INIT_SCRIPT", @<script type="text/javascript"> $(function() { $('table.sortable th').each(function(){ // . . . }); }); </script>)
Here’s the implementation that adds the Once
extension method to HtmlHelper
:
using System; using System.Web.Mvc; using System.Web.WebPages; namespace Foo { public static class HtmlUtils { public static HelperResult Once(this HtmlHelper html, string key, Func<object, HelperResult> template) { var httpContextItems = html.ViewContext.HttpContext.Items; var contextKey = "HtmlUtils.Once." + key; if (!httpContextItems.Contains(contextKey)) { // Render and record the fact in HttpContext.Items httpContextItems.Add(contextKey, null); return template(null); } else { // Do nothing, already rendered something with that key return new HelperResult(writer => { /*no-op*/ }); } } } }
Hope that helps!
I’m getting an error at the “var httpContextItems = html.ViewContext.HttpContext.Items” line. Should it work if put this as a class in the App_Code directory?
I got past the initial error but then got an error that there was no overload for 2 arguments. I created one that accepts two arguments and then passes those to the original method but it doesn’t like what I am providing as the third argument (using type Func).
What error? Which line? Also, I assume you are calling it from a Razor .cshtml file?
Yes it’s a Razor .cshtml file. The error “No overload for method ‘Once’ takes 2 arguments” occurs on the call to @Html.Once(“….”). I created an overload that accepts 2 arguments and then passes the original 2 arguments and a null value for the 3rd “template” argument to the original “Once” method but then I get “The best overloaded method match for…has some invalid arguments”.
Here is the method I created that accepts two arguments:
public static void Once(this HtmlHelper html, string key)
{
Func template = null;
Once(html, key, template);
}
Looks like some of my formatting was lost (angle brackets and the content inside) but as you can see I take the two arguments and add a third and pass those to the original method.
Hmm, I don’t know. Mine works as-is, just tried it in a new project. The code is just in a regular *.cs file, not in App_Code.
Also I don’t know what you’re doing with a null template. The whole point of this is to render a template once.
Thanks…I’ll keep looking at it. The reason I pass in the null is because I didn’t know what else to pass in as the third method argument.
“I didn’t know what else to pass in as the third method argument” – I think you may have missed the point of what this is for 🙂
Well MVC is a little outside of my comfort zone 🙂 For some reason it won’t compile unless I pass in something for the third parameter. I will try later today to move the code out of the app_code directory and see if that is the problem. Thanks!
I think I may see the source of my misunderstanding. The first parameter is implied and only the last two parameters need to be included in the call to the method: key and template…which is how your code is set up. I found another similar example and it got me clued in.
But apparently my project is looking for me to supply a value for the first argument: “this HtmlHelper html”.(???) At least now I know what is causing the issue and it should be easy to track down why it isn’t picking up that scope.