Заполнить секцию бритвы из частичного

103

Моя основная мотивация для попытки сделать это - получить Javascript, который требуется только для части в нижней части страницы с остальной частью Javascript, а не в середине страницы, где отображается часть.

Вот упрощенный пример того, что я пытаюсь сделать:

Вот макет с разделом скриптов прямо перед телом.

<!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>

Вот пример просмотра с использованием этого макета.

<h2>This is the view</h2>

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

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

А вот частичный рендеринг из вида.

<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>
}

В этом примере разметка, указанная в представлении, помещается в раздел, а разметка из партиала - нет. Можно ли с помощью Razor заполнить раздел из частичного представления? Если нет, то каковы другие методы получения Javascript, который нужен только частичным элементам внизу страницы, без его глобального включения?

Крейг М
источник
возможно, это проблема, потому что у вас есть другой раздел сценария в частичном ... IDK ... ваш код немного сбивает с толку ...
гидеон
Это не. Даже если раздел не отображается в представлении, код в партиале не попадает в финальную отрисованную страницу. Я думаю, что SLaks прав в том, что частичные элементы не могут участвовать в разделах родительского представления.
Craig M

Ответы:

78

Я решил эту проблему, написав пару методов расширения для класса HtmlHelper. Это позволяет частичным представлениям сказать, что им требуется сценарий, а затем в представлении макета, который записывает тег, который я вызываю в свой вспомогательный метод, чтобы испустить необходимые сценарии.

Вот вспомогательные методы:

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; }
}

Как только вы это сделаете, вам просто нужно вызвать частичное представление @Html.RequireScript("/Path/To/Script").

И в разделе заголовка макета вы вызываете @Html.EmitRequiredScripts().

Дополнительным преимуществом этого является то, что он позволяет отсеивать повторяющиеся запросы скриптов. Если у вас есть несколько представлений / частичных представлений, которым нужен данный сценарий, вы можете с уверенностью предположить, что вы выведете его только один раз

Г-н Белл
источник
Элегантное и чистое решение. +1
bevacqua
Я наткнулся на это решение после того, как выдернул большую часть моих волос - отличное решение ....
HIGGSY
Я не могу заставить это решение работать. Кажется, что EmitRequiredScripts () вызывается до того, как какие-либо частичные представления вызовут RequireScript (). Я делаю что-то неправильно?
Брайан Рот
Что-то не так, Брайан. Я активно использовал это решение в течение последнего года или около того, и оно хорошо работает. Может быть, опубликуйте новый вопрос с подробностями вашей проблемы и укажите ссылку здесь
Мистер Белл,
1
Есть ли здесь поддержка очистки кеша при развертывании новой версии приложения? Стандартный метод @ scripts.Render () вставляет в конце параметр URL, который генерируется во время сборки, чтобы браузер был вынужден получить последнюю версию при развертывании новой версии.
Саймон Грин,
28

Частичные представления не могут участвовать в разделах своих родительских представлений.

SLaks
источник
1
Это то, о чем я подозревал. Спасибо.
Craig M
@JohnBubriski Есть в Razor 2. Не знаю о пред. версии.
Shimmy Weitzhandler
@SLaks, почему так задумано? В моем сценарии у меня есть часть, которая является ротатором баннеров, я хочу, чтобы его скрипты / стили загружались только тогда, когда он включен, почему плохо загружать его встроенным?
Shimmy Weitzhandler
2
@Shimmy: Вам следует использовать систему управления ресурсами, такую ​​как Cassette.
SLaks
Спасибо. Я займусь этим.
Shimmy Weitzhandler
14

У вас может быть вторая часть, которая отвечает только за внедрение необходимого javascript. Разместите несколько скриптов вокруг @ifблоков, если хотите:

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

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

Очевидно, это можно было бы немного очистить, но тогда в Scriptsразделе вашего представления:

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

Опять же, он может и не получить приз за красоту, но он сработает.

Серги Папасейт
источник
1
Это довольно близко к тому, чем я закончил. Это определенно не красиво, но работает. Единственным недостатком этого является то, что вы не можете получить партиал через вызов ajax и включить JS. Я думаю, что в долгосрочной перспективе я закончу рефакторинг с использованием шаблонов jQuery и просто отправлю JSON с моих контроллеров вместо создания html на стороне сервера.
Craig M
@CraigM, вот куда я и направляюсь. MVC является законным, но (для меня) имеет гораздо больше смысла использовать шаблоны на стороне клиента (я изучаю Backbone.js), а затем нажимать / извлекать из API.
one.beat.consumer 03
@ one.beat.customer - Я использую шаблоны подчеркивания, так как я также использую Backbone, но я думаю о переключении либо на библиотеку Hogan из Twitter, либо на Plates из Nodejitsu. Оба имеют довольно приятные функции.
Craig M
10

Более элегантный способ сделать это - переместить сценарии частичного представления в отдельный файл и затем отобразить его в разделе представления сценариев:

<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>
}

Частичное представление _ Partial.cshtml :

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

Частичное представление _ PartialScripts.cshtml только со скриптами:

<script type="text/javascript">
    alert("I'm a partial script!");
</script>
Влад Руденко
источник
Это не так автоматично, как некоторые методы расширения или подключаемые модули, предлагаемые в других ответах, но у него есть преимущество простоты и ясности. Это нравится.
Марк Меуэр,
7

Установите пакет Nuget Forloop.HtmlHelpers - он добавляет несколько помощников для управления скриптами в частичных представлениях и шаблонах редактора.

Где-то в вашем макете нужно позвонить

@Html.RenderScripts()

Здесь будут выводиться любые файлы сценариев и блоки сценариев на странице, поэтому я бы рекомендовал поместить их после основных сценариев в макете и после раздела сценариев (если он у вас есть).

Если вы используете The Web Optimization Framework с объединением, вы можете использовать перегрузку

@Html.RenderScripts(Scripts.Render)

так что этот метод используется для записи файлов сценария.

Теперь, когда вы хотите добавить файлы сценариев или блоки в представление, частичное представление или шаблон, просто используйте

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

Помощники обеспечивают отображение только одной ссылки на файл сценария, если добавляются несколько раз, а также обеспечивают отображение файлов сценария в ожидаемом порядке, т.е.

  1. Макет
  2. Части и шаблоны (в том порядке, в котором они появляются в представлении, сверху вниз)
Расс Кэм
источник
5

[Обновленная версия] Обновленная версия после вопроса @Necrocubus для включения встроенных скриптов.

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
}

Мои 2 цента, это старый пост, но все еще актуальный, поэтому вот обновленное обновление решения г-на Белла, которое работает с ASP.Net Core.

Он позволяет добавлять сценарии и стили к основному макету из импортированных частичных представлений и подпредставлений, а также возможность добавлять параметры для импорта сценариев / стилей (например, async defer и т. Д.):

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; }
    }
}
Жан
источник
Спасибо, мужик! За него следует проголосовать больше, потому что он более актуален, чем ответ 6 лет назад.
Necroqubus,
Кроме того, можно ли изменить эти расширения, чтобы в качестве входных данных можно было использовать разделы скриптов? @ <text> </text> или что-то вроде разделов? В противном случае мне все еще нужен небольшой JS-скрипт для инициализации другого скрипта с переменными модели на стороне сервера: /
Necroqubus
@Necroqubus, вы можете проверить обновленную версию, но я ее еще не тестировал :)
Жан
Хорошо, я попробую проверить это для вас. Надеюсь, он работает с ASP.NET Core 1.0 MVC. Для контекста у меня есть несколько уровней вложенных партиалов, и я хочу, чтобы их скрипты отображались в нижнем колонтитуле.
Necroqubus
Не нужно <text>, просто добавьте его в виде строки (вы все равно можете использовать префикс @ "" для многострочных строк, если хотите), и без <script>тегов
Жан
1

Вы можете создать новую Layoutстраницу и обернуть PartialView внутри полного представления, которое отвечает за отображение содержимого, а также любых разделов библиотеки.

Например, допустим, у меня есть следующий код:

HomeController.cs

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

Когда отображается полная страница, она обычно отображается путем слияния двух файлов:

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 (или все, что указано в _ViewStart или переопределено на странице)

<!DOCTYPE html>

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

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

Теперь предположим, что вы хотите отрисовать About.cshtmlкак частичное представление , возможно, как модальное окно в ответ на вызов AJAX. Цель здесь - вернуть только содержимое, указанное на странице «О нас», сценариях и всем остальном, без излишков, включенных в _Layout.cshtmlглавный макет (например, полный <html>документ).

Вы можете попробовать это так, но в нем не будет ни одного из блоков раздела:

return PartialView("About", vm);

Вместо этого добавьте страницу с более простым макетом, например так:

_PartialLayout.cshtml

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

Или для поддержки такого модального окна:

_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>

Затем вы можете указать настраиваемое главное представление в этом контроллере или любом другом обработчике, который вы хотите отображать содержимое и скрипты представления одновременно.

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return !Request.IsAjaxRequest()
              ? View("About", vm)
              : View("About", "~/Views/Shared/_ModalLayout.cshtml", vm);
}
KyleMit
источник
1

Для тех, кто ищет версию aspnet core 2.0:

    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; }
        }
    }

Добавьте в свой макет после вызова раздела рендеринга скриптов:

@HttpContextAccessor.EmitRequiredScripts()

И на ваш частичный взгляд:

@inject IHttpContextAccessor HttpContextAccessor

...

@HttpContextAccessor.RequireScript("/scripts/moment.min.js")
Себастьян
источник
0

Основываясь на ответе г-на Белла и Шимми выше, я добавляю дополнительную функцию для сценария 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  

Пример в PartialView: - @ Html.RequireBundleStyles ("~ / bundles / fileupload / bootstrap / BasicPlusUI / css"); @ Html.RequireBundleScripts ("~ / bundles / fileupload / bootstrap / BasicPlusUI / js");

Пример на MasterPage: - @ Html.EmitRequiredBundleStyles ()

Харрис Йер
источник
0

Используйте @using(Html.Delayed()){ ...your content... }расширения из ответа https://stackoverflow.com/a/18790222/1037948, чтобы отображать любой контент (скрипты или просто HTML) позже на странице. Внутренний Queueдолжен обеспечивать правильный заказ.

Drzaus
источник
0

Эта функция также реализована в ClientDependency.Core.Mvc.dll. Он предоставляет html-помощники: @ Html.RequiresJs и @ Html.RenderJsHere (). Пакет Nuget: ClientDependency-Mvc

Эверт
источник
0

Здесь я нашел мое решение на часто задаваемые вопросы «как вставить разделы из частичных представлений в основные представления или основные представления макета для asp.net mvc?». Если вы выполните поиск в stackoverflow по ключевым словам «section + partial», вы получите довольно большой список связанных вопросов и ответов, но ни один из них не кажется мне элегантным с помощью грамматики бритвенного движка. Поэтому я просто посмотрю на движок Razor, чтобы узнать, есть ли лучшее решение этого вопроса.

К счастью, я обнаружил, что мне интересно, как движок Razor выполняет компиляцию файла шаблона представления (* .cshtml, * .vbhtml). (Я объясню позже), ниже мой код решения, который, на мой взгляд, довольно прост и достаточно элегантен в использовании.

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("");
        }
    }
}

Использование : Использовать код также довольно просто, и он выглядит почти так же, как и обычный. Он также поддерживает любые уровни вложенных частичных представлений. т.е. У меня есть цепочка шаблонов представления: _ViewStart.cshtml-> layout.cshtml-> index.cshtml -> [head.cshtml, foot.cshtml] -> ad.cshtml.

В layout.cshtml у нас есть:

<!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>

И в index.cshtml у нас есть:

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

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

А в head.cshtml у нас будет код:

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

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

это то же самое в foot.cshtml или ad.cshtml, вы все равно можете определить в них раздел Head или Foot, обязательно вызовите @ Html.EnsureSection () один раз в конце файла частичного представления. Это все, что вам нужно сделать, чтобы избавиться от данной проблемы в asp mvc.

Я просто делюсь своим фрагментом кода, чтобы другие могли его использовать. Если вы считаете, что это полезно, пожалуйста, не стесняйтесь оценивать мой пост. :)

Джонатан
источник