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()).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

Advertisements

19 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.

  7. Eric says:

    This will work too:

    @section Scripts {
    @Scripts.Render(“~/Scripts/your_script.js”)
    }

    It will render the script below the scripts set in the layout view.
    It’s a lot shorter and easier than the code in this article.

  8. Variant for Net Core:
    {code}
    public static class HtmlHelpers
    {
    private const string ScriptsKey = “DelayedScripts”;

    public static IDisposable BeginScripts(this IHtmlHelper helper)
    {
    return new ScriptBlock(helper.ViewContext);
    }

    public static HtmlString PageScripts(this IHtmlHelper helper)
    {
    return new HtmlString(string.Join(Environment.NewLine, GetPageScriptsList(helper.ViewContext.HttpContext)));
    }

    private static List GetPageScriptsList(HttpContext httpContext)
    {
    var pageScripts = (List)httpContext.Items[ScriptsKey];
    if (pageScripts == null)
    {
    pageScripts = new List();
    httpContext.Items[ScriptsKey] = pageScripts;
    }
    return pageScripts;
    }

    private class ScriptBlock : IDisposable
    {
    private readonly TextWriter _originalWriter;
    private readonly StringWriter _scriptsWriter;

    private readonly ViewContext _viewContext;

    public ScriptBlock(ViewContext viewContext)
    {
    _viewContext = viewContext;
    _originalWriter = _viewContext.Writer;
    _viewContext.Writer = _scriptsWriter = new StringWriter();
    }

    public void Dispose()
    {
    _viewContext.Writer = _originalWriter;
    var pageScripts = GetPageScriptsList(_viewContext.HttpContext);
    pageScripts.Add(_scriptsWriter.ToString());
    }
    }
    }
    {code}

  9. purvin from india says:

    This worked for me allowing me to co-locate javascript and html for partial view in same file. Helps with thought process to see html and related part in same partial view file.

    ——
    In View which uses Partial View called “_MyPartialView.cshtml”
    ——

    @Html.Partial(“_MyPartialView”,,
    new ViewDataDictionary { { “Region”, “HTMLSection” } } })

    @section scripts{

    @Html.Partial(“_MyPartialView”,,
    new ViewDataDictionary { { “Region”, “ScriptSection” } })

    }

    ——
    In Partial View file
    ——

    @model SomeType

    @{
    var region = ViewData[“Region”] as string;
    }

    @if (region == “HTMLSection”)
    {

    }

    @if (region == “ScriptSection”)
    {

    }

  10. It has actually been one of the best blogs i have checked out. It was actually very informative.Looking forward for a lot more blogs of this in near future

  11. Herman van der Blom says:

    Problem is when you load the Partial view dynamically then the master is not loaded.

  12. M Stickler says:

    This was exactly what I was looking for. Thank you!!!

  13. The moment making a new kitchen or renovating an existing kitchen, it is a good idea and a good idea to keep an open mind
    and find out everything possible – towards the end of the process, it will definitely pay off.

    Here are some common myths about the design process as well as its results:

    “You may easily add later” -the best planning is to take into account
    and add all the characteristics of the kitchen at the planning and
    as well as or renewal stages, and not at a later stage.
    For example, in case you are not thinking about setting up a flat screen TELEVISION in the
    kitchen space or if you do not currently have a water dispenser, the best
    thing is to make for future installation of these and plan power, communication and water factors and keep the choices open up.
    Also, since the kitchen is supposed to provide
    us for quite some time, it is a good idea to measure all existing ergonomic solutions such as
    drawer drawers, bathroom drawer style dishwashers, and so forth Even if
    you are young and fit at the moment, these additions will prove themselves Mobility in the future.
    Either way, if you prefer to deal with kitchen planning
    in stages – one aspect or specific area at a time – make certain to plan carefully so that everything will fit in stylistic and
    functional harmony when the kitchen will finally
    be complete.

    ” I do not have so many things” – whether we like it or not,
    your kitchen has gigantic potential for ruining due to multitude of items trapped in it, which is why space is plenty to store, This means that
    there is a relative shortage of overhead storage units, so it is important to balance function and style.

    “Bigger Better” – Your kitchen space is large and
    you need a sizable kitchen, but then you will find yourself
    going back and on from one machine to another, in addition to this situation, preferably in the look stages,
    consider adding a job sink beside the refrigerator or Reduce
    the need to cross these distances, and there is without a doubt that these improvements mean additional costs, and if your
    budget is limited, it is unquestionably well worth considering a compact kitchen.

    “I do not desire a designer ” -not every kitchen design project requires
    a designer, but many people need someone to help us see the overall picture and administer
    the work. Renting consult with an architect or designer does involve quite a
    lot of cost, but in the long run it is all about conserving money and time.
    Although you may have a very clear picture of what you
    want, do not save the expense of a designer since it can best fulfill ideal.

    Proper and efficient planning of your kitchen and coordination between all the different professionals included in building your kitchen by a designer
    could keep your project within it is as well as budget, save you unnecessary
    money and pointless problems and particularly keep your sanity.

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s

%d bloggers like this: