Note: The accepted solution won't work for partial views as the question asks for.
The Problem
In the normal flow, you can define the contents for a particular section from inside of the parent view on your ActionResult using a <code>@section <i>SectionName</i> {}</code> declaration. And when that view is finally inserted into its LayoutPage, it can call RenderSection
to place those contents anywhere within the page, allowing you to define some inline JavaScript that can be rendered and parsed at the bottom of the page after any core libraries that it depends on like this:
The problem arises when you want to be able to reuse the full page view inside of a partial view. Perhaps you'd like to also re-use the view as a widget or dialog from inside of another page. In which case, the full Partial View is rendered in its entirety wherever you've placed the call to @Html.EditorFor
or @Html.Partial
inside of the Parent View like this:
According to the MSDN Docs on Layouts with Razor Syntax:
- Sections defined in a view are available only in its immediate layout page.
- Sections cannot be referenced from partials, view components, or other parts of the view system.
- The body and all sections in a content page must all be rendered by the layout page
In that scenario, it becomes tricky to get the script defined into the partial view to the bottom of the page. Per the docs, you can only call RenderSection
from the layout view and you cannot define the @section
contents from inside of a partial view, so everything gets lumped into the same area and your script will be rendered, parsed, and run from the middle of your HTML page, instead of at the bottom, after any libraries it might depend on.
The Solution
For a full discussion of the many ways to inject sections from partial views into your page, I'd start with the following two questions on StackOverflow:
The varying solutions therein differ on support for nesting, ordering, multiple script support, different content types, calling syntax, and reusability. But however you slice it, pretty much any solution will have to accomplish two basic tasks:
- Gradually build script objects onto your request from within any page, partial view, or template, probably leveraging some kind of HtmlHelper extension for reusability.
- Render that script object onto your layout page. Since the layout page actually renders last, this is simply emitting the object we've been building onto the master page.
Here's a simple implementation by Darin Dimitrov
Add the Helper Extension Methods which will allow you to build arbitrary script objects into the ViewContent.HttpContext.Items
collection and subsequently fetch and render them later.
Utilities.cs
<!-- language: lang-js -->
public static class HtmlExtensions
{
public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
{
htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
return MvcHtmlString.Empty;
}
public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
{
foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys)
{
if (key.ToString().StartsWith("_script_"))
{
var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>;
if (template != null)
{
htmlHelper.ViewContext.Writer.Write(template(null));
}
}
}
return MvcHtmlString.Empty;
}
}
Then you can use like this within your application
Build this script objects like this inside of your Partial View like this:
<!-- language: lang-html -->
<pre><code><b>@Html.Script(</b>
<i>@<script>
$(function() {
$("#@Html.IdFor(model => model.FirstName)").change(function() {
alert("New value is '" + this.value + "'");
});
})
</script></i>
<b>)</b>
</code></pre>
And then render them anywhere within your LayoutPage like this:
<!-- language: lang-cs -->
<pre><code>@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
<b>@Html.RenderScripts()</b>
</code></pre>