7

In ASP.NET Core MVC it is possible to define a script section for a page like this:

@section scripts {
    <script>
        alert('hello');
    </script>
}

And if the the layout contains :

@RenderSection("Scripts", required: false)

your script(s) will be rendered. This come in handy per example to guarantee that the scripts will be rendered after all javascript includes like jQuery.

But how to render a script in a partial view?

Ivan Valadares
  • 773
  • 6
  • 17
  • Generally this is a bad idea because you aren't bundling/minifying your scripts. – Erik Philips Jul 07 '19 at 00:22
  • 1
    @ErikPhilips that not true, imagine that i want a specific javascript code that only run in that partial. why should i bundle it and import all over the application? And for minifying, i can create my typescript file minifyied and import it on the partial inside my script block. – Ivan Valadares Jul 08 '19 at 11:30
  • Possible duplicate of [Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View Engine](https://stackoverflow.com/questions/7556400/injecting-content-into-specific-sections-from-a-partial-view-asp-net-mvc-3-with) – chakeda Jul 08 '19 at 16:18

3 Answers3

12

Here is a solution :

In Layout page :

@Html.PageScripts()

In the Partial :

@using (Html.BeginScripts())
{
 <script>
   alert('hello');
 </script>
}

And The Helper Class for MVC.core

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using System;
using System.Collections.Generic;
using System.IO;

namespace MyProjectNamespace
{
    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<string> GetPageScriptsList(HttpContext httpContext)
        {
            var pageScripts = (List<string>)httpContext.Items[ScriptsKey];
            if (pageScripts == null)
            {
                pageScripts = new List<string>();
                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());
            }
        }
    }
}

Tip: import you class helper in _ViewImports.cshtml so you can use it in all views.

Erik Philips
  • 51,408
  • 11
  • 123
  • 146
Ivan Valadares
  • 773
  • 6
  • 17
  • Similar to my [Answer](https://stackoverflow.com/questions/13764936/how-to-render-a-section-in-a-partial-view-in-mvc3/13765578#13765578) that I no longer use because of my [Answer Here](https://stackoverflow.com/a/56938853/209259). – Erik Philips Jul 08 '19 at 16:16
  • 1
    Just want to say thank you for this, I made a very elegant solution out of this snippet for development purposes :) – Lennard Fonteijn Nov 24 '19 at 20:40
4

Generally this is a bad idea because you aren't bundling/minifying your scripts.

@ErikPhilips that not true, imagine that i want a specific javascript code that only run in that partial. why should i bundle it and import all over the application? And for minifying, i can create my typescript file minifyied and import it on the partial inside my script block.

imagine that i want a specific javascript code that only run in that partial.

The script won't only run in that partial it will run for the entire page. It's client side code delivered in a single http call (assuming normal usage because you haven't specified anything else). Consider the partial:

@Model SomeModel
<div class='my-component'>
<div>
<script>
  $('.my-component').css('width', model.Width);
</script>

Not reusable because all components same page will be the same width regardless of model.

Instead you can create a single script file and use data-* attributes to store configuration information and let the the single script figure it out (like many MANY libraries do, for example bootstrap):

<div class='my-component green' data-config='{ "width": 200, "height": 200 }'>
</div>

then in the single script file:

$(document).ready(function(){
  $('.my-component').each(function(){
    var $this = $(this);
    var config = $this.data('config');
    $this.css("width", config.width);
    $this.css("height", config.height);
  });
});

why should i bundle it

Because then it's cached by the browser automatically. That means less to download every instance. Consider the following:

<div class='my-component'>
<div>
<script>
  // lots of scripts say 100-200 lines of it.
</script>

Every time the client visits any page, they have to download probably the same exact code every time, possibly multiple times per page. That takes up both client bandwidth and server bandwidth that is not necessary.

and import all over the application?

You import it once, either globally or maybe once in a specific layout. Because after the first time, it's cached.

And for minifying, i can create my typescript file minifyied and import it on the partial inside my script block.

Then why have script in the partial at all.

Recommended Reading: Decoupling Your HTML, CSS, and JavaScript

Erik Philips
  • 51,408
  • 11
  • 123
  • 146
  • 2
    This is some common sense that quickly gets thrown out the window when people want to do it the easy (short-term, of course) way. It can take some effort to implement this (say, if you want to use @Url.Action() in your script) but it's worth it. – perustaja Mar 19 '20 at 20:16
0

I had a similar problem. I had a view with a null Layout. I just wanted to include a bundle of scripts.

So, inside the body tag I added the reference to the bundle ...

<html>
<body>
<div>
// some html here ...
        </div>
//include the bundle ...
        @Scripts.Render("~/bundles/jqueryval")
   
</body>

</html>
Mike
  • 401
  • 4
  • 5