2

I have section for scripts in my _Layout.cshtml:

<html>
    <body>
    ...
        @RenderSection("FooterScript", required: false)
    </body>
</html>

I have view "Index.cshtml" that contains @Html.RenderPartial("LinksBlock", someModel). LinksBlock partial requires script file "links.js". I want to make links.js inclusion into FooterScript from my partial view, not from main view (main view don't know about dependencies of partial view), and I want to be sure, that if I use more than 1 LinksBlock in my view, only 1 links.js was included. Is it possible?

David Levin
  • 6,573
  • 5
  • 48
  • 80

1 Answers1

14

Sections do not work with partial views. But you could write a pair of custom helpers that could be used in conjunction:

public static class HtmlExtensions
{
    public static IHtmlString RegisteredScripts(this HtmlHelper htmlHelper)
    {
        var ctx = htmlHelper.ViewContext.HttpContext;
        var registeredScripts = ctx.Items["_scripts_"] as Stack<string>;
        if (registeredScripts == null || registeredScripts.Count < 1)
        {
            return null;
        }
        var sb = new StringBuilder();
        foreach (var script in registeredScripts)
        {
            var scriptBuilder = new TagBuilder("script");
            scriptBuilder.Attributes["type"] = "text/javascript";
            scriptBuilder.Attributes["src"] = script;
            sb.AppendLine(scriptBuilder.ToString(TagRenderMode.Normal));
        }
        return new HtmlString(sb.ToString());
    }

    public static void RegisterScript(this HtmlHelper htmlHelper, string script)
    {
        var ctx = htmlHelper.ViewContext.HttpContext;
        var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        var registeredScripts = ctx.Items["_scripts_"] as Stack<string>;
        if (registeredScripts == null)
        {
            registeredScripts = new Stack<string>();
            ctx.Items["_scripts_"] = registeredScripts;
        }
        var src = urlHelper.Content(script);
        if (!registeredScripts.Contains(src))
        {
            registeredScripts.Push(src);
        }
    }
}

And then in your _Layout.cshtml:

@Html.RegisteredScripts()

and in your partial:

@{Html.RegisterScript("~/scripts/foo.js");}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • What if I want to have possibility to add both script separated files and scripts that placed in partial view (for second I want to use key to identify and not register same scripts if partial view renders multiple times)? – David Levin Apr 04 '12 at 11:02
  • I use .vsdoc files and for second way I want to retain Intellisence support (e.g. It should not work like Register("key", "") but Register("key", @{})) – David Levin Apr 04 '12 at 11:30
  • 1
    Great. Upvoted. I'm not going to use this yet but I want to add it to my codebase right now!!! :) – usr-local-ΕΨΗΕΛΩΝ May 04 '13 at 17:44
  • 1
    Darin, you are a godsend!! The solution is fantastic and we have put it to use immediately. Thank you-- We have made one modification, we are not sure about the use of Stack here (last in first out), so we have replaced it with a Queue (and instead of the foreach loop we use Dequeue to gradually empty out the collection). Was the use of Stack intentional and we have failed to see its benefits? – timmi4sa May 27 '14 at 16:42