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

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());
            }
        }

        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

About these ads

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

  1. The following line is giving me an error:

    pageScripts.Add((StringWriter)this.webPageBase.OutputStack.Pop());

    Error:

    Error 46 Argument 1: cannot convert from ‘System.IO.StringWriter’ to ‘string’

  2. UPDATE: I fixed the above mentioned error by replacing the offending line with:

    pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString());

  3. Alexander says:

    Thanks for a very helpful post! It’s a most useful and elegant solution I ever found.

  4. This is good – although is there any way to register page scripts when you are loading partial views dynamically (i.e. using jQuery.get)?

  5. www says:

    whoah this weblog is magnificent i like studying your articles.
    Stay up the great work! You know, many people are hunting around for
    this information, you could help them greatly.

  6. Otto Gebb says:

    Thanks!
    It works with the correction proposed by Jean-François Beauchamp.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: