简体   繁体   中英

How to use @section scripts in a partial view MVC.Core

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?

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.

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

Sections don't work in partial views and that's by design. You may use some custom helpers to achieve similar behavior, but honestly it's the view's responsibility to include the necessary scripts, not the partial's responsibility. I would recommend using the @scripts section of the main view to do that and not have the partials worry about scripts.

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>

Similar to Ivan's answer, but based on a TagHelper and Mvc6+. It's probably best to just move framework scripts that won't change often to the top of the page, but here it is anyway.

Usage

scripts:

<script location="page">
    console.log('hello world!');
</script>

_Layout.cshtm registration:

@Html.RegisterPageScripts()

Implementation

Html helper:

public static class HtmlHelpers
{
    private const string ScriptsKey = "__PageScripts";

    public static void AddCachedPageScripts(this HttpContext httpContext, string script)
    {
        var scripts = httpContext.GetCachedPageScripts();
        scripts.Add(script);
        httpContext.Items[ScriptsKey] = scripts;
    }

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

    private static List<string> GetCachedPageScripts(this HttpContext httpContext)
    {
        return (List<string>)httpContext.Items[ScriptsKey] ?? new List<string>();
    }
}

Tag helper:

/// <summary>
/// Extends the default script tag by adding support write scripts to @Html.RegisterPageScripts(), usually placed at the end of _Layout.cshtml.
/// </summary>
/// <example>
/// Example _ViewImports.cshtml:
///     @namespace DefaultNamespace.Pages
///     @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
///     @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.TextAreaTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
///     @addTagHelper DefaultNamespace.TagHelpers.CustomTextAreaTagHelper, DefaultNamespace
///
/// Example Markup Usage:
///     &lt;script location="page">foo();&lt;/script>
///
/// Example _Layout.cshtml Registration:
///     @Html.RegisterPageScripts()
/// </example>
[HtmlTargetElement("script", Attributes = LOCATION_ATTRIBUTE_NAME, TagStructure = TagStructure.NormalOrSelfClosing)]
public class CustomScriptTagHelper : TagHelper
{
    private const string LOCATION_ATTRIBUTE_NAME = "location";
    private const string SCRIPT_END_TAG = "</script>";
    private const string SCRIPT_LOCATION_MOVED_MESSAGE = $"<!-- Script tag moved to @Html.{nameof(HtmlHelpers.RegisterPageScripts)}(). -->";

    //public CustomScriptTagHelper(IHtmlGenerator generator) : base()
    //{
    //}

    public enum Locations
    {
        inline,
        page
    }

    [HtmlAttributeName(LOCATION_ATTRIBUTE_NAME)]
    public Locations Location { get; set; }

    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (Location == Locations.page)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            if (output == null) throw new ArgumentNullException(nameof(output));

            //get rendered script tag//
            string renderedTag;
            using (var writer = new StringWriter())
            {
                output.WriteTo(writer, NullHtmlEncoder.Default);
                renderedTag = writer.ToString();
                var index = renderedTag.LastIndexOf(SCRIPT_END_TAG);
                renderedTag = renderedTag.Remove(index, SCRIPT_END_TAG.Length);
            }

            //register content as script//
            var childContext = output.GetChildContentAsync().Result;
            var script = childContext.GetContent();
            ViewContext.HttpContext.AddCachedPageScripts($"{renderedTag}\n{script}\n{SCRIPT_END_TAG}");

            //replace content with comment//
            output.SuppressOutput();
            output.Content.AppendHtml(SCRIPT_LOCATION_MOVED_MESSAGE);
        }
        base.Process(context, output);
    }
}

I can't find exact answer this problem. So I solved it and here it is.

In partial view @section scripts not working. So delete it and write your scripts inside script tags only.

But if you use external library like jquery, its not working. So you have to add your scripts inside load method which I wrote below.

window.addEventListener("load", (event) => {
    $(document).on("click", "#btnClick", function () {
        window.location.href = "Index.html";
    });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM