Внедрение содержимого в определенные разделы из частичного представления ASP.NET MVC 3 с помощью Razor View Engine

324

У меня есть этот раздел, определенный в моем _Layout.cshtml

@RenderSection("Scripts", false)

Я могу легко использовать это из вида:

@section Scripts { 
    @*Stuff comes here*@
}

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

Давайте предположим, что это моя страница просмотра:

@section Scripts { 

    <script>
        //code comes here
    </script>
}

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>

Мне нужно вставить какой-то контент в Scriptsраздел из _myPartialчастичного представления.

Как я могу это сделать?

tugberk
источник
17
для тех, кто придет к этому позже - есть пакет nuget
Russ Cam
@RussCam ты должен ответить на этот вопрос. +1 пакет nuget решает точную проблему, с которой сталкивается ОП.
Кэрри Кендалл,
1
Пакет @RussCam NuGet не является решением, код пакета может быть.
Максим Ви.
8
@MaksimVi. ну, я написал пакет nuget и не собираюсь его удалять , поэтому вместо повторения кода ( bitbucket.org/forloop/forloop-htmlhelpers/src ) или вики ( bitbucket.org/forloop/forloop-htmlhelpers/wiki) / Home ) здесь, ссылка на него в качестве комментария соответствует духу stackoverflow, IMO.
Расс Кэм
Вот еще одно решение, которое кажется очень хорошим: stackoverflow.com/questions/5355427/…
jkokorian,

Ответы:

235

Разделы не работают в частичном представлении, и это по замыслу. Вы можете использовать несколько пользовательских помощников для достижения аналогичного поведения, но, честно говоря, ответственность за включение необходимых сценариев лежит на представлении, а не на частичном. Я бы порекомендовал использовать раздел @scripts на главном экране, чтобы не беспокоиться о скриптах по частям.

Дарин димитров
источник
445
Но что, если сценарий очень специфичен для частичного? Разве не имеет логического смысла определять его в частичном, а не в представлении?
Еж
43
Почему это дизайн?
Шимми Вейцхандлер
56
@ Darin: я не согласен. Как насчет принципа СУХОЙ? Я не люблю повторяться, даже если это только ссылки на сценарии.
fretje
14
@fretje, каждый имеет право высказать свое мнение по теме. Я уважаю вашу. В своем ответе я высказал свое и связал с ответом, который позволил бы вам выполнить эту задачу. Но я также подчеркнул, что я бы порекомендовал и сделал для этой ситуации.
Дарин Димитров
33
приставка @JoshNoe и все остальное - «виджет» (отображение + расширенное взаимодействие) - прекрасный пример частичного представления, тесно связанного со связанным javascript. По замыслу мне не нужно было писать два оператора включения в разных местах, чтобы получить полную функциональность, потому что отображение никогда не будет без сопутствующего взаимодействия, и взаимодействие никогда не появится в другом месте.
drzaus
83

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

По моему у _layoutменя есть:

@RenderSection("body_scripts", false)

На мой indexвзгляд, я имею:

@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

На мой clientsвзгляд у меня есть (все карты и доц. Html):

@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

Мой Clients_Scriptsвид содержит JavaScript, который будет отображаться на странице

Таким образом, мой сценарий изолирован и может быть отображен на странице там, где требуется, с body_scriptsтегом, отображаемым только при первом появлении, когда механизм просмотра бритвы находит его.

Это позволяет мне отделить все - это решение, которое работает довольно хорошо для меня, у других могут быть проблемы с ним, но оно исправляет дыру «по замыслу».

Дэн Ричардсон
источник
2
Я не был тем, кто голосовал против вас, но я скажу, что мне не очень нравится это решение, потому что оно по-прежнему отделяет сценарии для конкретного представления от самого представления.
раздавить
3
20 других людей и у меня другое мнение. Вы по-прежнему можете иметь сценарии, непосредственно связанные с представлением, которые находятся в отдельном файле, это ошибка программирования, если вы не включаете свой сценарий вместе со своим представлением. Наличие этого в отдельном файле отделяет взаимодействие от представления и позволяет изобилию других выгод от того, что это находится в отдельном файле.
Дэн Ричардсон
1
Ты совершенно прав. Я на самом деле полностью согласен и предпочитаю этот метод лично. Настоящая проблема для меня заключается в том, что мои коллеги борются с таким большим разделением. Это проблема домена, хотя. Я думаю, что этот метод идеален, особенно если учесть процесс сборки JavaScript. Я продолжу работать над обучением моих коллег использованию этого метода и полностью поддерживаю его. Я думаю, что ваш ответ может быть улучшен, хотя. Вам не нужно было упоминать «20 человек согласны». Тот факт, что ответ популярен, не всегда означает, что он правильный. В этом случае это правильно.
раздавить
Очень верно, и я всегда рад принять конструктивные отзывы и изменить свой собственный код и ответить, если есть улучшения :)
dan richardson
1
Это решение имеет дополнительное преимущество, заключающееся в том, что он по-прежнему может выполнять все функции MVC, которые вы ожидаете сделать в типичном представлении, например, возможность JSON-кодирования переданного в Model и генерирования URL-адресов с использованием Url. Действие. Этот подход является элегантным способом настройки контроллеров AngularJS - каждый частичный вид может представлять отдельный контроллер в модуле Angular. Так чисто!
Дэн
40

Из решений в этой теме я пришел к следующему, вероятно, слишком сложному решению, которое позволяет отложить рендеринг любого html (также скриптов) в блоке using.

ИСПОЛЬЗОВАНИЕ

Создать «раздел»

  1. Типичный сценарий: при частичном просмотре включайте блок только один раз, независимо от того, сколько раз частичное представление повторяется на странице:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. В частичном представлении включите блок для каждого использования частичного:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. В частичном представлении включайте блок только один раз, независимо от того, сколько раз повторяется частичное, но позже визуализируйте его конкретно по имени when-i-call-you:

    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        <b>show me once by name</b>
        <span>@Model.First().Value</span>
    }

Визуализировать «разделы»

(т.е. отобразить отложенный раздел в родительском представлении)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`

КОД

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
    /// </summary>
    private class DelayedInjectionBlock : IDisposable {
        /// <summary>
        /// Unique internal storage key
        /// </summary>
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// <summary>
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// </summary>
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// <summary>
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// </summary>
        private const string EMPTY_IDENTIFIER = "";

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// <summary>
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// </summary>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// <summary>
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
        /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// <summary>
        /// Append the internal content to the context's cached list of output delegates
        /// </summary>
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// <summary>
    /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
    /// <para>
    /// <example>
    /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show at later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
    /// <code>
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     <b>show me once</b>
    ///     <span>@Model.First().Value</span>
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
    /// <returns>using block to wrap delayed output</returns>
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// <summary>
    /// Render all queued output blocks injected via <see cref="Delayed"/>.
    /// <para>
    /// <example>
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show me later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>more for later</b>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
    /// <code>
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// </code>
    /// </example>
    /// </para>

    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="removeAfterRendering">only render this once</param>
    /// <returns>rendered output content</returns>
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}
drzaus
источник
1
Ничего себе, мне даже сложно понять код, но +1 за то, что придумал решение
Rameez Ahmed Sayad
@RameezAhmedSayad вы правы - возвращаясь сюда, даже я смущен тем, как я хотел сказать, как его использовать. Обновление ответа ...
drzaus
И чтобы уточнить далее - причина в том, что есть два «имени», состоит в том, что если вы хотите, чтобы он отображался только один раз, когда ему нужен уникальный ключ в параметре isOnlyOne, но только если вы хотите отобразить его в определенном месте по имени, вы предоставляете идентификатор в противном случае это сбрасывается в Html.RenderDelayed().
drzaus
Лично я не думаю, что возникнет необходимость в покупке проблемы и использовании этого подхода, раздел в частичных представлениях просто не нужен, так как его можно устранить, и сценарии могут идти туда без определения раздела. Это потому, что он визуализируется извне, и если вы видите код для визуализированной страницы, вы просто замечаете, что код для частичного представления там не виден. Так что, если это вопрос лучшей организации и т. Д., Это никак не повлияет.
Превосходно
@Transcendent «дебаты» уже начались в комментариях к принятому ответу stackoverflow.com/a/7556594/1037948
drzaus
16

У меня была эта проблема и я использовал эту технику.

Это лучшее решение, которое я нашел, которое очень гибкое.

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

МГНБ
источник
9

Если у вас есть законная необходимость запустить некоторые jsиз них partial, вот как вы можете это сделать jQuery:

<script type="text/javascript">        
    function scriptToExecute()
    {
        //The script you want to execute when page is ready.           
    }

    function runWhenReady()
    {
        if (window.$)
            scriptToExecute();                                   
        else
            setTimeout(runWhenReady, 100);
    }
    runWhenReady();
</script>
Серж Саган
источник
Я попытался @drzaus, ему нужен SeeIfReady или он не работает.
Качо Санта
8

Следуя ненавязчивому принципу, для _myPartial совсем не обязательно вводить контент непосредственно в раздел скриптов. Вы можете добавить эти скрипты частичного представления в отдельный .jsфайл и ссылаться на них в разделе @scripts из родительского представления.

леканора
источник
10
Что произойдет, если частичное представление вообще не отображается на странице? Мы по-прежнему ссылаемся на эти файлы .js в parent и делаем их перегруженными?
Мурали Муругесан
5

В нашем мышлении есть фундаментальный недостаток, особенно при использовании MVC. Недостаток в том, что JavaScript как-то отвечает за представление. Представление - это представление, JavaScript (поведенческий или иной) - это JavaScript. В паттернах Silverlight и WPF MVVM мы сталкиваемся с «видом сначала» или «моделью сначала». В MVC мы всегда должны пытаться рассуждать с точки зрения модели, и JavaScript является частью этой модели во многих отношениях.

Я бы предложил использовать шаблон AMD (мне самому нравится RequireJS ). Разделяйте ваш JavaScript в модулях, определяйте свою функциональность и подключайтесь к html из JavaScript вместо того, чтобы полагаться на представление для загрузки JavaScript. Это очистит ваш код, разделит ваши проблемы и облегчит жизнь одним махом.

Г-н Боден
источник
Примерно два или три месяца я пользуюсь RequireJS и не думаю, что когда-либо разработаю другое веб-приложение без RequireJS.
Tugberk
6
JavaScript также может быть обязанностью View.
Кельмен
1
Использование шаблона AMD - хорошая идея, но я не согласен с вашим утверждением, что JavaScript является частью модели. Часто определяется поведение View, особенно в сочетании с чем-то вроде Knockout. Вы помещаете JSON-представление вашей модели в представление JavaScript. Лично я просто использую замыкания, пользовательское «пространство имен» для windowобъекта и включаю библиотечные сценарии перед любыми частями.
раздавить
Я думаю, что здесь есть недоразумение. При разработке большинства веб-приложений мы фактически разрабатываем два приложения: одно, которое работает на сервере, и другое, которое работает на клиенте. С точки зрения сервера все, что вы отправляете в браузер, является «представлением». В этом смысле JavaScript является частью представления. С точки зрения клиентского приложения, чистый HTML - это представление, а JS - это код, который соответствует M и C в терминах MVC сервера. Я думаю, именно поэтому люди не согласны здесь.
Агент
3

Цель ОП заключается в том, что он хочет определить встроенные сценарии в своем частичном представлении, которое, как я предполагаю, является специфичным только для этого частичного представления, и включить этот блок в свой раздел сценариев.

Я понял, что он хочет, чтобы этот Частичный Взгляд был самодостаточным. Идея аналогична компонентам при использовании Angular.

Мой способ состоял бы в том, чтобы просто сохранить сценарии в Частичном представлении как есть. Теперь проблема заключается в том, что при вызове Partial View он может выполнить сценарий там раньше всех других сценариев (который обычно добавляется в конец страницы макета). В этом случае у вас есть только скрипт Partial View, ожидающий другие скрипты. Есть несколько способов сделать это. Самый простой, который я использовал раньше, это использование события наbody .

На моем макете у меня было бы кое-что на основании как это:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
  (function(){
    document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
  })();
</script>

Тогда на моем частичном виде (внизу):

<script>
  (function(){
    document.querySelector('body').addEventListener('scriptsLoaded', function() {

      // .. do your thing here

    });
  })();
</script>

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

аланы
источник
2

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

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

Iridio
источник
Только что попробовал; к сожалению, это не работает (создал ViewBag.RenderScripts = new List<string>();в верхней части главной страницы, затем вызвал @Html.Partial("_CreateUpdatePartial",Model,ViewData), затем поместил @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. В частичном представлении я поставил @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan
2

Вам не нужно использовать разделы в частичном представлении.

Включить в свой частичный вид. Он выполняет функцию после загрузки jQuery. Вы можете изменить условие условия для вашего кода.

<script type="text/javascript">    
var time = setInterval(function () {
    if (window.jQuery != undefined) {
        window.clearInterval(time);

        //Begin
        $(document).ready(function () {
           //....
        });
        //End
    };
}, 10); </script>

Хулио Спейдер

Хулио Спейдер
источник
2

Вы можете использовать эти методы расширения : (Сохранить как PartialWithScript.cs)

namespace System.Web.Mvc.Html
{
    public static class PartialWithScript
    {
        public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
        {
            if (htmlHelper.ViewBag.ScriptPartials == null)
            {
                htmlHelper.ViewBag.ScriptPartials = new List<string>();
            }

            if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
            {
                htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
            }

            htmlHelper.ViewBag.ScriptPartialHtml = true;
            htmlHelper.RenderPartial(partialViewName);
        }

        public static void RenderPartialScripts(this HtmlHelper htmlHelper)
        {
            if (htmlHelper.ViewBag.ScriptPartials != null)
            {
                htmlHelper.ViewBag.ScriptPartialHtml = false;
                foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
                {
                    htmlHelper.RenderPartial(partial);
                }
            }
        }
    }
}

Используйте как это:

Пример частичный: (_MyPartial.cshtml) Поместите html в if, а js в else.

@if (ViewBag.ScriptPartialHtml ?? true)
    <p>I has htmls</p>
}
else {
    <script type="text/javascript">
        alert('I has javascripts');
    </script>
}

В вашем _Layout.cshtml или там, где вы хотите, чтобы выполнялись скрипты из партиалов, поместите следующее (один раз): он будет отображать только javascript всех партиалов на текущей странице в этом месте.

@{ Html.RenderPartialScripts(); }

Затем, чтобы использовать ваш частичный, просто сделайте это: он будет отображать только HTML в этом месте.

@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}
Lomak
источник
1

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

Вот как выглядит вставка раздела в частичном представлении:

@model KeyValuePair<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}

А на странице вставляем частичный вид ...

@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))

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

Наслаждайтесь!

Плутон
источник
1
Подскажите пожалуйста и ссылку на полностью рабочий проект?
Эхсан Заргар Ершади
1

Идея Плутона в лучшем виде:

CustomWebViewPage.cs:

    public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {

    public IHtmlString PartialWithScripts(string partialViewName, object model) {
        return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
    }

    public void RenderScriptsInBasePage(HelperResult scripts) {
        var parentView = ViewBag.view as WebPageBase;
        var parentHtml = ViewBag.html as HtmlHelper;
        parentView.DefineSection("scripts", () => {
            parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
        });
    }
}

Views \ web.config:

<pages pageBaseType="Web.Helpers.CustomWebViewPage">

Посмотреть:

@PartialWithScripts("_BackendSearchForm")

Частичное (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

@helper scripts() {
<script>
    //code will be rendered in a "scripts" section of the Layout page
</script>
}

Страница макета:

@RenderSection("scripts", required: false)
PaulSanS
источник
1

Это сработало для меня, позволив мне совмещать javascript и html для частичного просмотра в одном файле. Помогает в мыслительном процессе, чтобы увидеть HTML и связанные части в одном файле частичного просмотра.


В представлении, которое использует частичное представление, называемое "_MyPartialView.cshtml"

<div>
    @Html.Partial("_MyPartialView",< model for partial view>,
            new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>

@section scripts{

    @Html.Partial("_MyPartialView",<model for partial view>, 
                  new ViewDataDictionary { { "Region", "ScriptSection" } })

 }

В файле частичного просмотра

@model SomeType

@{
    var region = ViewData["Region"] as string;
}

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}
Purvin
источник
0

Я решил это совершенно другим путем (потому что я спешил и не хотел реализовывать новый HtmlHelper):

Я обернул свой частичный вид в большое выражение if-else:

@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}

Затем я дважды вызвал Partial с пользовательскими данными ViewData:

@Html.Partial("MyPartialView", Model, 
    new ViewDataDictionary { { "ShouldRenderScripts", false } })

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}
Рик Лав
источник
Конечно, вся идея заключается в том, что потребителю частичного представления не нужно знать, что он должен включать в себя сценарии, это своего рода проблема? В противном случае вы можете просто сказать @Html.Partial("MyPartialViewScripts")
Дэн Ричардсон
Нет, идея состоит в том, чтобы позволить сценариям быть определенными в том же документе, что и HTML, но я согласен, что это не идеально.
Рик Лав
0

У меня была похожая проблема, где у меня была главная страница следующим образом:

@section Scripts {
<script>
    $(document).ready(function () {
        ...
    });
</script>
}

...

@Html.Partial("_Charts", Model)

но частичное представление зависело от некоторого JavaScript в разделе Scripts. Я решил это путем кодирования частичного представления в виде JSON, загрузки его в переменную JavaScript и последующего использования этого для заполнения div, поэтому:

@{
    var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}

@section Scripts {
<script>
    $(document).ready(function () {
        ...
        var partial = @partial;
        $('#partial').html(partial.html);
    });
</script>
}

<div id="partial"></div>
Джон М
источник
IMO, вы должны были решить эту проблему, переместив свой JS в отдельный файл.
Достойно7
0

по выбору, вы можете использовать вашу папку / index.cshtml в качестве главной страницы, а затем добавить сценарии раздела. Затем, в вашем макете у вас есть:

@RenderSection("scripts", required: false) 

и ваш index.cshtml:

@section scripts{
     @Scripts.Render("~/Scripts/file.js")
}

и он будет работать над всеми вашими частичными взглядами. Это работает для меня

RogerEdward
источник
0

Используя Mvc Core, вы можете создать аккуратный TagHelper, scriptsкак показано ниже. Это может быть легко преобразовано в sectionтег, где вы также даете ему имя (или имя взято из производного типа). Обратите внимание, что внедрение зависимости должно быть настроено для IHttpContextAccessor.

При добавлении скриптов (например, в частичном)

<scripts>
    <script type="text/javascript">
        //anything here
    </script>
</scripts>

При выводе скриптов (например, в файле макета)

<scripts render="true"></scripts>

Код

public class ScriptsTagHelper : TagHelper
    {
        private static readonly object ITEMSKEY = new Object();

        private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;

        private IHttpContextAccessor _httpContextAccessor;

        public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var attribute = (TagHelperAttribute)null;
            context.AllAttributes.TryGetAttribute("render",out attribute);

            var render = false;

            if(attribute != null)
            {
                render = Convert.ToBoolean(attribute.Value.ToString());
            }

            if (render)
            {
                if (_items.ContainsKey(ITEMSKEY))
                {
                    var scripts = _items[ITEMSKEY] as List<HtmlString>;

                    var content = String.Concat(scripts);

                    output.Content.SetHtmlContent(content);
                }
            }
            else
            {
                List<HtmlString> list = null;

                if (!_items.ContainsKey(ITEMSKEY))
                {
                    list = new List<HtmlString>();
                    _items[ITEMSKEY] = list;
                }

                list = _items[ITEMSKEY] as List<HtmlString>;

                var content = await output.GetChildContentAsync();

                list.Add(new HtmlString(content.GetContent()));
            }
        }
    }
BlackjacketMack
источник
0

На днях я столкнулся с почти идентичной проблемой, за исключением того, что частичное представление было ответом на запрос AJAX. В моей ситуации частичное было фактически полной страницей, но я хотел, чтобы оно было доступно как частичное с других страниц.

Если вы хотите визуализировать разделы частично, самое чистое решение - создать новый макет и использовать переменную ViewBag. Это не работает с @Html.Partial()или новым <partial></partial>, используйте AJAX.

Основной вид (который вы хотите визуализировать как частичное в другом месте):

@if(ViewBag.Partial == true) {
    Layout = "_layoutPartial";
}

<div>
    [...]
</div>    

@section Scripts {
    <script type="text/javascript">
        [...]
    </script>
}

контроллер:

public IActionResult GetPartial() {

    ViewBag.Partial = true;

    //Do not return PartialView!
    return View("/path/to/view")
}

_layoutPartial.cshtml (new):

@RenderSection("Scripts")
@RenderBody()

Тогда используйте AJAX на своей странице.

Если вы хотите отобразить страницу в главном макете (не частичную), не устанавливайте ViewBag.Partial = true. Нет необходимости в помощниках HTML.

KatoFett
источник
-1

Ну, я полагаю, что другие авторы предоставили вам средство для непосредственного включения @section в ваш фрагмент (с помощью сторонних помощников html).

Но я считаю, что, если ваш сценарий тесно связан с вашим частичным, просто поместите свой javascript прямо внутри встроенного <script>тега в вашем частичном и покончите с этим (просто будьте осторожны с дублированием скрипта, если вы собираетесь использовать частичное более одного раза в едином представлении);

CShark
источник
1
Обычно это не идеально, потому что загрузка jQuery и т. Д. Происходит после встроенных скриптов ... но для нативного кода, я думаю, это нормально.
Достойно7
-3

Предположим, у вас есть частичное представление _contact.cshtml, ваш контакт может быть юридическим (имя) или физическим субъектом (имя, фамилия). Ваше мнение должно позаботиться о том, что отображается, и что может быть достигнуто с помощью JavaScript. поэтому может потребоваться отложенный рендеринг и JS изнутри.

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

Также обратите внимание, что у MVC 6 будет так называемый View Component, даже у фьючерсов MVC были некоторые подобные вещи, и Telerik также поддерживает такую ​​вещь ...

user4298890
источник
1
3 года спустя, и я не думаю, что это вообще отвечает на вопрос? Что ты здесь пытаешься сказать? Ответ на вопрос 3 года спустя со спекулятивными особенностями будущих технологий не является действительно ответом или особенно полезным
Дэн Ричардсон,
-3

Я только добавил этот код в свое частичное представление и решил проблему, хотя и не очень чисто, она работает. Вы должны убедиться, что идентификаторы объектов, которые вы отображаете.

<script>
    $(document).ready(function () {
        $("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
    });
</script>
луис
источник
-5

У меня была похожая проблема, решенная с помощью этого:

@section ***{
@RenderSection("****", required: false)
}

Это хороший способ сделать инъекцию.

Поуриа Джафари
источник