Wypełnij sekcję maszynki do golenia z części

103

Moją główną motywacją do zrobienia tego jest uzyskanie JavaScript, który jest wymagany tylko przez częściową część na dole strony z resztą JavaScript, a nie na środku strony, na której jest renderowana część.

Oto uproszczony przykład tego, co próbuję zrobić:

Oto układ z sekcją Skrypty tuż przed treścią.

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />    
</head>

<body>
    @RenderBody()
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
    @RenderSection("Scripts", false)
</body>
</html>

Oto przykładowy widok wykorzystujący ten układ.

<h2>This is the view</h2>

@{Html.RenderPartial("_Partial");}

@section Scripts {
<script type="text/javascript">
        alert("I'm a view.");
</script>
}

A oto fragment renderowany z widoku.

<p>This is the partial.</p>

@* this never makes it into the rendered page *@
@section Scripts {
<script type="text/javascript">
    alert("I'm a partial."); 
</script>
}

W tym przykładzie znacznik określony w widoku jest umieszczany w sekcji, ale znacznik z części nie jest. Czy można wypełnić sekcję z częściowego widoku za pomocą Razor? Jeśli nie, jakie są inne metody uzyskiwania JavaScript, które są potrzebne tylko przez częściowe u dołu strony, bez uwzględniania go globalnie?

Craig M.
źródło
może jest to problem, ponieważ masz inną sekcję skryptu w częściowym .. IDK .. Twój kod jest trochę zagmatwany ..
gideon
To nie jest. Nawet jeśli sekcja jest pozostawiona poza widokiem, kod w części nie trafia do ostatecznej renderowanej strony. Myślę, że SLaks ma rację, ponieważ częściowe nie mogą uczestniczyć w sekcjach widoku nadrzędnego.
Craig M

Odpowiedzi:

78

Sposób, w jaki sobie z tym poradziłem, to napisanie kilku metod rozszerzających do klasy HtmlHelper. Dzięki temu widoki częściowe mogą powiedzieć, że wymagają skryptu, a następnie w widoku układu, który zapisuje tag, który wywołuję, do mojej metody pomocniczej, aby emitować wymagane skrypty

Oto metody pomocnicze:

public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
    if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
    return null;
}

public static HtmlString EmitRequiredScripts(this HtmlHelper html)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) return null;
    StringBuilder sb = new StringBuilder();
    foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
    {
        sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
    }
    return new HtmlString(sb.ToString());
}
public class ResourceInclude
{
    public string Path { get; set; }
    public int Priority { get; set; }
}

Gdy już to zrobisz, Twój częściowy widok musi tylko zadzwonić @Html.RequireScript("/Path/To/Script") .

I w sekcji head widoku układu, którą wywołujesz @Html.EmitRequiredScripts() .

Dodatkową zaletą tego jest to, że umożliwia to wyeliminowanie zduplikowanych żądań skryptu. Jeśli masz wiele widoków / widoków częściowych, które wymagają danego skryptu, możesz bezpiecznie założyć, że wygenerujesz go tylko raz

Panie Bell
źródło
Eleganckie i czyste rozwiązanie. +1
bevacqua
Właśnie natknąłem się na to rozwiązanie po
wyrwaniu
Nie mogę sprawić, by to rozwiązanie zadziałało. Wygląda na to, że EmitRequiredScripts () jest wywoływana, zanim jakiekolwiek częściowe widoki zostaną wywołane RequireScript (). czy robię coś źle?
Bryan Roth
Coś nie brzmi dobrze, Bryan. Korzystałem z tego rozwiązania w ciągu ostatniego roku i działa dobrze. Może zadaj nowe pytanie ze szczegółami swojego problemu i umieść tutaj link do adresu URL
Mr Bell
1
Czy to obsługuje pomijanie pamięci podręcznej podczas wdrażania nowej wersji aplikacji? Metoda out-of-box @ scripts.Render () umieszcza na końcu parametr adresu URL, który jest generowany w czasie kompilacji, dzięki czemu przeglądarka jest zmuszona pobrać najnowszą wersję po wdrożeniu nowej wersji.
Simon Green
28

Częściowe widoki nie mogą uczestniczyć w sekcjach ich widoków nadrzędnych.

SLaks
źródło
1
To jest to, co podejrzewałem. Dzięki.
Craig M
@JohnBubriski Jest w Razor 2. Nie wiem o prev. wersje.
Shimmy Weitzhandler
@SLaks, dlaczego jest to zgodne z projektem? W moim scenariuszu mam część, która jest rotatorem banerów, chcę, aby jego skrypty / style ładowały się tylko wtedy, gdy są włączone, dlaczego źle jest ładować je w tekście?
Shimmy Weitzhandler
2
@Shimmy: Powinieneś użyć systemu zarządzania zasobami, takiego jak Cassette.
SLaks
Dziękuję Ci. Przyjrzę się temu.
Shimmy Weitzhandler
14

Możesz mieć drugą część, która jest odpowiedzialna tylko za wstrzyknięcie niezbędnego javascript. @ifJeśli chcesz, umieść tam kilka skryptów wokół bloków:

@model string
@if(Model == "bla") {
    <script type="text/javascript">...</script>
}

@else if(Model == "bli") {
    <script type="text/javascript">...</script>
}

Można to oczywiście trochę wyczyścić, ale w Scriptsczęści widoku:

@section Scripts
{
    @Html.Partial("_Scripts", "ScriptName_For_Partial1")
}

Ponownie, może nie wygrać nagrody piękności, ale zadziała.

Sergi Papaseit
źródło
1
Jest to bardzo zbliżone do tego, co zrobiłem. Na pewno nie jest ładny, ale działa. Jedynym minusem jest to, że nie można uzyskać częściowej za pośrednictwem wywołania Ajax i mieć dołączony JS. Myślę, że na dłuższą metę zakończę refaktoryzację przy użyciu szablonów jQuery i po prostu wyślę JSON z moich kontrolerów, zamiast budować HTML po stronie serwera.
Craig M
@CraigM też tam zmierzam. MVC jest legalne, ale o wiele bardziej sensowne (dla mnie) jest używanie szablonów po stronie klienta (patrzę na Backbone.js), a następnie wypychanie / ściąganie z API.
one.beat.consumer
@ one.beat.customer - używam szablonów podkreślenia od czasu, gdy używam Backbone, ale myślę o przełączeniu się na bibliotekę Hogan z Twittera lub Plates z Nodejitsu. Obie mają całkiem fajne funkcje.
Craig M
10

Bardziej eleganckim sposobem jest przeniesienie skryptów częściowego widoku do osobnego pliku, a następnie wyrenderowanie go w sekcji Skrypty widoku:

<h2>This is the view</h2>

@Html.RenderPartial("_Partial")

@section Scripts
{
    @Html.RenderPartial("_PartialScripts")

    <script type="text/javascript">
        alert("I'm a view script.");
    </script>
}

Widok częściowy _ Partial.cshtml :

<p>This is the partial.</p>

Częściowy widok _ PartialScripts.cshtml tylko ze skryptami:

<script type="text/javascript">
    alert("I'm a partial script!");
</script>
Vlad Rudenko
źródło
Nie jest to tak automatyczne, jak niektóre metody rozszerzeń lub wtyczki sugerowane w innych odpowiedziach, ale ma tę zaletę, że jest prostsze i przejrzyste. To się podoba.
Mark Meuer,
7

Zainstaluj plik Forloop.HtmlHelpers pakiet - dodaje kilka pomocników do zarządzania skryptami w widokach częściowych i szablonach edytorów.

Gdzieś w swoim układzie musisz zadzwonić

@Html.RenderScripts()

Będzie to miejsce, w którym wszelkie pliki skryptów i bloki skryptów zostaną wyprowadzone na stronę, więc zalecam umieszczenie ich po głównych skryptach w układzie i po sekcji skryptów (jeśli taką masz).

Jeśli używasz platformy optymalizacji sieci Web z pakietowaniem, możesz użyć przeciążenia

@Html.RenderScripts(Scripts.Render)

tak aby ta metoda była używana do zapisywania plików skryptów.

Teraz, gdy chcesz dodać pliki skryptów lub bloki w widoku, częściowym widoku lub szablonie, po prostu użyj

@using (Html.BeginScriptContext())
{
  Html.AddScriptFile("~/Scripts/jquery.validate.js");
  Html.AddScriptBlock(
    @<script type="text/javascript">
       $(function() { $('#someField').datepicker(); });
     </script>
  );
}

Pomocnicy zapewniają, że tylko jedno odwołanie do pliku skryptu jest renderowane, jeśli jest dodawane wiele razy, a także zapewnia, że ​​pliki skryptów są renderowane w oczekiwanej kolejności, tj.

  1. Układ
  2. Części i szablony (w kolejności, w jakiej pojawiają się w widoku, od góry do dołu)
Russ Cam
źródło
5

[Zaktualizowana wersja] Zaktualizowana wersja po pytaniu @Necrocubus w celu uwzględnienia wbudowanych skryptów.

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_INLINESCRIPT = "RequiredInlineScript";
    const string REQ_STYLE = "RequiredStyle";

    #region Scripts
    /// <summary>
    /// Adds a script 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, bool bottom=false, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options, Type=ResourceType.Script, Bottom=bottom});
        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="script"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <returns></returns>
    public static string RequireInlineScript(this IHtmlHelper html, string script, int priority = 1, bool bottom = false)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        if (requiredScripts == null) ctxt.Items[REQ_INLINESCRIPT] = requiredScripts = new List<InlineResource>();
        requiredScripts.Add(new InlineResource() { Content=script, Priority = priority, Bottom=bottom, Type=ResourceType.Script});
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredScripts(false)
    /// at the end of your head tag and 
    /// @Html.EmitRequiredScripts(true) at the end of the body if some scripts are set to be at the bottom.
    /// </summary>
    public static HtmlString EmitRequiredScripts(this IHtmlHelper html, bool bottom)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        var requiredInlineScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        var scripts = new List<Resource>();
        scripts.AddRange(requiredScripts ?? new List<ResourceToInclude>());
        scripts.AddRange(requiredInlineScripts ?? new List<InlineResource>());
        if (scripts.Count==0) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in scripts.Where(s=>s.Bottom==bottom).OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Scripts

    #region Styles
    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredStyles()
    /// at the end of your head tag
    /// </summary>
    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Styles

    #region Models
    public class InlineResource : Resource
    {
        public string Content { get; set; }
        public override string ToString()
        {
            return "<script>"+Content+"</script>";
        }
    }

    public class ResourceToInclude : Resource
    {
        public string Path { get; set; }
        public string[] Options { get; set; }
        public override string ToString()
        {
            switch(Type)
            {
                case ResourceType.CSS:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", Path);
                    else
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", Path, String.Join(" ", Options));
                default:
                case ResourceType.Script:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\"></script>\n", Path);
                    else
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", Path, String.Join(" ", Options));
            }
        }
    }
    public class Resource
    {
        public ResourceType Type { get; set; }
        public int Priority { get; set; }
        public bool Bottom { get; set; }
    }
    public enum ResourceType
    {
        Script,
        CSS
    }
    #endregion Models
}

Moje 2 centy, to stary post, ale nadal aktualny, więc tutaj jest ulepszona aktualizacja rozwiązania pana Bella, które działa z ASP.Net Core.

Umożliwia dodawanie skryptów i stylów do głównego układu z zaimportowanych widoków częściowych i podglądów podrzędnych, a także możliwość dodawania opcji do importu skryptów / stylów (takich jak opóźnienie asynchronizacji itp.):

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_STYLE = "RequiredStyle";

    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredScripts(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            else
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", item.Path, String.Join(" ", item.Options));

        }
        return new HtmlString(sb.ToString());
    }


    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", item.Path);
            else
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", item.Path, String.Join(" ", item.Options));
        }
        return new HtmlString(sb.ToString());
    }


    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
        public string[] Options { get; set; }
    }
}
Drelich
źródło
Dziękuje! To powinno być bardziej przychylne, ponieważ jest bardziej istotne niż odpowiedź, która ma 6 lat.
Necroqubus
Czy można też zmodyfikować te rozszerzenia, aby umożliwić wprowadzanie sekcji skryptów? @ <text> </text> czy coś w rodzaju sekcji? W przeciwnym razie nadal potrzebuję małego skryptu JS do zainicjowania drugiego skryptu ze zmiennymi modelu po stronie serwera: /
Necroqubus
@Necroqubus możesz sprawdzić zaktualizowaną wersję, jednak jeszcze jej nie testowałem :)
Jean
W porządku, spróbuję to dla Ciebie przetestować. Mam nadzieję, że działa z ASP.NET Core 1.0 MVC. Dla kontekstu mam wiele poziomów zagnieżdżonych części składowych i chcę, aby ich skrypty były renderowane w stopce.
Necroqubus
Nie potrzebujesz <text>, po prostu dodaj go jako ciąg (nadal możesz przedrostek @ "" dla wielu linii, jeśli wolisz) i bez <script>tagów
Jean
1

Możesz utworzyć nową Layoutstronę i zawinąć PartialView wewnątrz pełnego widoku, który jest odpowiedzialny za renderowanie zawartości, a także wszelkich sekcji biblioteki.

Na przykład, powiedzmy, że mam następujący kod:

HomeController.cs

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return View("About", vm);
}

Renderowany widok pełnej strony jest zwykle renderowany przez scalenie dwóch plików:

About.cshtml

@model AboutViewModel

@{
    ViewBag.Title = "About CSHN";
}

<h3>@ViewBag.Title</h3>

@section Styles {
    <style> /* style info here */ </style>
}

@section Scripts {
    <script> /* script info here */ </script>
}

_Layout.cshtml (lub cokolwiek jest określone w _ViewStart lub nadpisane na stronie)

<!DOCTYPE html>

<html>
<head>
    @RenderSection("Styles", false)
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderBody()

    @RenderSection("scripts", false)
</body>
</html>

Teraz załóżmy, że chcesz renderować About.cshtmljako widok częściowy , być może jako okno modalne w odpowiedzi na wywołanie AJAX. Celem jest zwrócenie tylko treści określonej na stronie About, skryptów i tak dalej, bez całego nadmiaru zawartego w układzie _Layout.cshtmlgłównym (jak pełny<html> dokument).

Możesz spróbować tego w ten sposób, ale nie będzie zawierał żadnego z bloków sekcji:

return PartialView("About", vm);

Zamiast tego dodaj prostszą stronę układu, taką jak ta:

_PartialLayout.cshtml

<div>
    @RenderBody()
    @RenderSection("Styles", false)
    @RenderSection("scripts", false)
</div>

Lub do obsługi okna modalnego, takiego jak to:

_ModalLayout.cshtml

<div class="modal modal-page fade" tabindex="-1" role="dialog" >
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">

            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title">@ViewBag.Title</h4>
            </div>

            <div class="modal-body">

                @RenderBody()
                @RenderSection("Styles", false)
                @RenderSection("scripts", false)

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-inverse" data-dismiss="modal">Dismiss</button>
            </div>
        </div>
    </div>
</div>

Następnie możesz określić niestandardowy widok główny w tym kontrolerze lub innym module obsługi, który chcesz jednocześnie renderować zawartość i skrypty widoku

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return !Request.IsAjaxRequest()
              ? View("About", vm)
              : View("About", "~/Views/Shared/_ModalLayout.cshtml", vm);
}
KyleMit
źródło
1

Dla osób poszukujących wersji 2.0 aspnet core:

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Http;

    public static class HttpContextAccessorExtensions
    {
        public static string RequireScript(this IHttpContextAccessor htmlContextAccessor, string path, int priority = 1)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) htmlContextAccessor.HttpContext.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
            if (requiredScripts.All(i => i.Path != path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
            return null;
        }

        public static HtmlString EmitRequiredScripts(this IHttpContextAccessor htmlContextAccessor)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) return null;
            StringBuilder sb = new StringBuilder();
            foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
            {
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            }
            return new HtmlString(sb.ToString());
        }
        public class ResourceInclude
        {
            public string Path { get; set; }
            public int Priority { get; set; }
        }
    }

Dodaj do układu po wywołaniu sekcji renderowania skryptów:

@HttpContextAccessor.EmitRequiredScripts()

I z twojego częściowego punktu widzenia:

@inject IHttpContextAccessor HttpContextAccessor

...

@HttpContextAccessor.RequireScript("/scripts/moment.min.js")
Sebastian
źródło
0

Bazując na powyższej odpowiedzi Mr Bell And Shimmy, dodaję dodatkową funkcję dla skryptu Bundle.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Mvc;
namespace ABC.Utility
{
public static  class PartialViewHelper
{
    public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
        return null;
    }

    public static string RequireBundleStyles(this HtmlHelper html, string bundleName)
    {
        var a = System.Web.Optimization.Styles.Render(bundleName);
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) HttpContext.Current.Items["RequiredStyles"] = requiredStyles = a;
        return null;
    }

    public static string RequireBundleScripts(this HtmlHelper html, string bundleName)
    {
        var a=System.Web.Optimization.Scripts.Render(bundleName);
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = a;
        return null;
    }

    public static HtmlString EmitRequiredBundleStyles(this HtmlHelper html)
    {
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) return null;
        return MvcHtmlString.Create(requiredStyles.ToHtmlString()) ;
    }

    public static HtmlString EmitRequiredBundleScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) return null;
        return MvcHtmlString.Create(requiredScripts.ToHtmlString());
    }

    public static HtmlString EmitRequiredScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
        }
        return new HtmlString(sb.ToString());
    }
    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
    }
}//end class
}// end namespace  

Przykład w PartialView: - @ Html.RequireBundleStyles ("~ / bundles / fileupload / bootstrap / BasicPlusUI / css"); @ Html.RequireBundleScripts ("~ / bundles / fileupload / bootstrap / BasicPlusUI / js");

Próbka na MasterPage: - @ Html.EmitRequiredBundleStyles ()

Harris Yer
źródło
0

Użyj @using(Html.Delayed()){ ...your content... }rozszerzeń z answer https://stackoverflow.com/a/18790222/1037948, aby renderować dowolną zawartość (skrypty lub tylko HTML) w dalszej części strony. Wewnętrzny Queuepowinien zapewnić prawidłowe uporządkowanie.

drzaus
źródło
0

Ta funkcjonalność jest również zaimplementowana w ClientDependency.Core.Mvc.dll. Dostarcza pomocników html: @ Html.RequiresJs i @ Html.RenderJsHere (). Pakiet NuGet: ClientDependency-Mvc

Wynicować
źródło
0

Oto moje rozwiązanie często zadawanych pytań „jak wstrzyknąć sekcje z widoków częściowych do widoków głównych lub głównego widoku układu dla asp.net mvc?”. Jeśli przeszukasz stackoverflow za pomocą słów kluczowych „sekcja + częściowe”, otrzymasz całkiem dużą listę powiązanych pytań i udzielonych odpowiedzi, ale żadne z nich nie wydaje mi się eleganckie dzięki gramatyce maszynki do golenia. Rzucę więc okiem na silnik Razor i zobaczę, czy może być lepsze rozwiązanie tego pytania.

Na szczęście znalazłem coś interesującego dla mnie w tym, jak silnik Razor wykonuje kompilację dla pliku szablonu widoku (* .cshtml, * .vbhtml). (Wyjaśnię później), poniżej jest mój kod rozwiązania, które moim zdaniem jest dość proste i wystarczająco eleganckie w użyciu.

namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        /// <summary>
        /// 确保所有视图,包括分部视图(PartialView)中的节(Section)定义被按照先后顺序追加到最终文档输出流中
        /// </summary>
        public static MvcHtmlString EnsureSection(this HtmlHelper helper)
        {
            var wp = (WebViewPage)helper.ViewDataContainer;
            Dictionary<string, WebPages.SectionWriter> sw = (Dictionary<string, WebPages.SectionWriter>)typeof(WebPages.WebPageBase).GetProperty("SectionWriters", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance).GetValue(wp);
            if (!helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = new Dictionary<string, Stack<WebPages.SectionWriter>>();
                helper.ViewContext.HttpContext.Items["SectionWriter"] = qss;
            }
            var eqs = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
            foreach (var kp in sw)
            {
                if (!eqs.ContainsKey(kp.Key)) eqs[kp.Key] = new Stack<WebPages.SectionWriter>();
                eqs[kp.Key].Push(kp.Value);
            }
            return MvcHtmlString.Create("");
        }

        /// <summary>
        /// 在文档流中渲染指定的节(Section)
        /// </summary>
        public static MvcHtmlString RenderSectionEx(this HtmlHelper helper, string section, bool required = false)
        {
            if (helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
                if (qss.ContainsKey(section))
                {
                    var wp = (WebViewPage)helper.ViewDataContainer;
                    var qs = qss[section];
                    while (qs.Count > 0)
                    {
                        var sw = qs.Pop();
                        var os = ((WebViewPage)sw.Target).OutputStack;
                        if (os.Count == 0) os.Push(wp.Output);
                        sw.Invoke();
                    }
                }
                else if (!qss.ContainsKey(section) && required)
                {
                    throw new Exception(string.Format("'{0}' section is not defined.", section));
                }
            }
            return MvcHtmlString.Create("");
        }
    }
}

stosowanie : Używanie kodu jest również dość proste i wygląda prawie tak samo jak zwykle. Obsługuje również wszystkie poziomy zagnieżdżonych widoków częściowych. to znaczy. Mam łańcuch szablonów widoku: _ViewStart.cshtml-> layout.cshtml-> index.cshtml -> [head.cshtml, foot.cshtml] -> ad.cshtml.

W layout.cshtml mamy:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>@ViewBag.Title - @ViewBag.WebSetting.Site.WebName</title>
    <base href="@ViewBag.Template/" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1.0, user-scalable=0,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <meta name="renderer" content="webkit">
    <meta name="author" content="Taro Technology Co.,LTD" />
    <meta name="robots" content="index,follow" />
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <link rel="alternate icon" type="@ViewBag.WebSetting.Site.WebFavIcon" href="@ViewBag.WebSetting.Site.WebFavIcon">
    @Html.RenderSectionEx("Head")
</head>
<body>
    @RenderBody()
    @Html.RenderSectionEx("Foot")
</body>
</html>

W pliku index.cshtml mamy:

@{
    ViewBag.Title = "首页";
}

@Html.Partial("head")
<div class="am-container-1">
    .......
</div>
@Html.Partial("foot")

A w head.cshtml mielibyśmy kod:

@section Head{
    <link rel="stylesheet" href="assets/css/amazeui.css" />
    <link rel="stylesheet" href="assets/css/style.css" />
}

<header class="header">
   ......
</header>
@Html.EnsureSection()

tak samo jest w foot.cshtml lub ad.cshtml, nadal możesz zdefiniować w nich sekcję Head lub Foot, upewnij się, że wywołałeś @ Html.EnsureSection () raz na końcu pliku częściowego widoku. To wszystko, co musisz zrobić, aby pozbyć się poddanego problemowi w asp mvc.

Po prostu udostępniam fragment kodu, aby inni mogli z niego skorzystać. Jeśli uważasz, że jest to przydatne, nie wahaj się i oceń mój post. :)

Jonathan
źródło