Rendering scripts from partial views at the end of the page in MVC (Razor)

February 16, 2011

Everyone knows scripts should be placed at the bottom of the page.

If you have a partial view in MVC, it may be necessary to render scripts specific to this view, and a common approach is to include the scripts (not necessarily inline) in the html mark up of the view.

The problem with this is that you may be depending on library scripts which are not loaded yet because they are the last thing in your Layout/Master.

One option is to change the design so that rather than calling Html.Partial to render a partial view, the partial view becomes the view, and the view becomes a master/layout. You can now use DefineSection, RenderBody, RenderSection etc. This however was not an option for me for several reasons.

Here is the solution I came up with which will only work for rendering content at the end of the document. It won’t work for injecting content into the head (e.g. css) because it runs in the order you expect. The scripts are registered in during the call to Html.Partial, which (usually) comes after the head section, meaning the head section is already done and its too late. In fact, I have even named the functions BeginScripts and Html.PageScripts instead of something more generic to make it clear it is only really applicabale for scripts at the end of the page.

On the partial views:

@using (Html.BeginScripts())
{
    <script src="@Url.Content("~/Scripts/script.js")" type="text/javascript"></script>
}

And (pretty much) last thing in the Layout/Master:

@Html.PageScripts()

And the helpers themselves:

    public static class HtmlHelpers
    {
        private class ScriptBlock : IDisposable
        {
            private const string scriptsKey = "scripts";
            public static List<string> pageScripts
            {
                get
                {
                    if (HttpContext.Current.Items[scriptsKey] == null)
                        HttpContext.Current.Items[scriptsKey] = new List<string>();
                    return (List<string>)HttpContext.Current.Items[scriptsKey];
                }
            }

            WebViewPage webPageBase;

            public ScriptBlock(WebViewPage webPageBase)
            {
                this.webPageBase = webPageBase;
                this.webPageBase.OutputStack.Push(new StringWriter());
            }

            public void Dispose()
            {
                pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString());
            }
        }

        public static IDisposable BeginScripts(this HtmlHelper helper)
        {
            return new ScriptBlock((WebViewPage)helper.ViewDataContainer);
        }

        public static MvcHtmlString PageScripts(this HtmlHelper helper)
        {
            return MvcHtmlString.Create(string.Join(Environment.NewLine, ScriptBlock.pageScripts.Select(s => s.ToString())));
        }
    }

It shouldn’t take much work to improve the above code to operate more like the ClientScriptManager of asp.net… use a dictionary instead of a list, and require a key to verify the same script can’t be registered twice. Alternativly (what i’ve done in the project I’m working on), you could accept an optional list of library dependencies in BeginScripts, and simply make sure these have been rendered before outputting the scripts themseleves

Simples