Сделайте это только один раз для всех частичных представлений MVC: Безопасен ли Context.Items (поток)?

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

Я хотел бы иметь возможность в частичном представлении добавить скрипт javascript только один раз для всей страницы.
Я читал, что могу использовать Context.Items для обмена данными (например, логическое значение ScriptAlreadyIncluded) между всеми (частичными) представлениями.

Но могу ли я на это положиться? Или я должен использовать что-то еще?


Мой текущий код (наиболее релевантным частичным представлением является ITypedModel.cshtml и AFieldFormula_DirectFieldFormula.cshtm)

Index.cshtml

@model IEnumerable<MygLogWeb.Classes.MemberField>

@{
    Layout = "~/Views/Shared/_Layout.cshtml";

    var uid = Guid.NewGuid().ToString();
}

@using (Html.BeginForm("Index", "MemberField", FormMethod.Post, new { id = uid }))
{

    @Html.AntiForgeryToken()

    <table class="table none">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.IsSystem)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.IsVirtual)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Name)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Formula)
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @Html.EditorFor(model => model)
        </tbody>
        <tfoot>
            <tr>
                <td colspan="5">
                    <input type="button" value="@Resources.Common.Buttons.add" />
                </td>
            </tr>
        </tfoot>
    </table>

    <input type="submit" value="@Resources.Common.Buttons.save"/>
}

<script type="text/javascript">
    new TableEdit("@uid");
</script>

EditorTemplates/MemberField.cshtml

@model MygLogWeb.Classes.MemberField

<tr>
    <td>
        @Html.DisplayFor(model => model.IsSystem)
        @Html.HiddenFor(model => model.IsSystem)
    </td>
    <td>
        @Html.DisplayFor(model => model.IsVirtual)
        @Html.HiddenFor(model => model.IsVirtual)
    </td>
    <td>
        @if (Model.IsSystem)
        {
            @Html.DisplayFor(model => model.Name)
            @Html.HiddenFor(model => model.Name)
        }
        else
        {
            @Html.TextBoxFor(model => model.Name, new { data_focus = "true" })
            @Html.ValidationMessageFor(model => model.Name)
        }
    </td>
    <td>
        @Html.EditorFor(model => model.Formula, "ITypedModel")
    </td>
    <td>
        @if (!Model.IsSystem)
        {
            <input type="button" value="@Resources.Common.Buttons.delete" data-code="delete" />
        }
    </td>
</tr>

EditorTemplates/ITypedModel.cshtml

...
@foreach (var item in items)
{
    <span data-refered-type="@item.Item.Value">
        @if (item.Type.IsSubclassOf(typeof(AFieldFormula)))
        {
            var newModel = item.Item.Selected ? Model : Activator.CreateInstance(item.Type);

            @Html.Partial("~/Views/MemberField/EditorTemplates/AFieldFormula_" + item.Type.Name + ".cshtml", newModel)
        }
    </span>
}

EditorTemplates/AFieldFormula_ConstantFormula.cshtml

@model MygLogWeb.Classes.ConstantFormula

<b>ConstantFormula</b>
@Html.EditorFor(model => model.Constant)
@Html.ValidationMessageFor(model => model.Constant)

EditorTemplates/AFieldFormula_DirectFieldFormula.cshtml

@model MygLogWeb.Classes.DirectFieldFormula

...Build a JSON dictionnary...

<b>DirectFieldFormula</b>
@Html.EditorFor(model => model.Field)
@Html.ValidationMessageFor(model => model.Field)

...Some controls using the JSON dictionnary...

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


person Serge    schedule 05.11.2013    source источник


Ответы (4)


Является ли Context.Items (поток) безопасным?

Нет, как говорит Джеймс в своем ответе.

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

А Context.Items предназначен для совместного использования объектов между различными частями конвейера обработки запросов.

person Joe    schedule 05.11.2013
comment
Я не знаю, как MVC обрабатывает потоки. Он мог бы использовать несколько на запрос (как это делает Microsoft для своей технологии Sql Server). Тем не менее, из того, что вы говорите, я предполагаю, что это так же просто, как один запрос = один поток. - person Serge; 05.11.2013

На основе идеи @James и этот пост

Я мог бы просто использовать: ViewContext.Controller.ViewBag, так как у меня есть только один контроллер для всей страницы (я не использую там ChildActions).

Это дает:

@model MygLogWeb.Classes.DirectFieldFormula

@{
    var GlobalViewBag = ViewContext.Controller.ViewBag;
    if (GlobalViewBag.DirectFieldFormula_ScriptAlreadyIncluded as bool? != true)
    {
        <script type="text/javascript">
            // ...
        </script>
    }

    GlobalViewBag.DirectFieldFormula_ScriptAlreadyIncluded = true;
}

...
person Serge    schedule 06.11.2013

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

Согласно Microsoft, HttpContext не является потокобезопасным. Хорошая новость заключается в том, что существует способ реализовать повторно используемое решение, которое будет использовать HttpContext только в потоке, управляемом ASP.NET, как это рекомендовано Microsoft. Основное ограничение заключается в том, что этот метод может отслеживать только сценарии, которые были добавлены с его помощью, но сценарий, созданный специально для добавления функциональности к частичному представлению, в любом случае следует использовать только с частичным представлением. Увидеть ниже.

Вот исходное место, где я оставил этот ответ. Он использует HttpContext.Current.Items для поддержки статического класса singleton-per-request, содержащего словарь сценариев, которые уже были включены (используя этот метод).

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

Реализуйте расширение для класса HtmlHelper и закрытый резервный класс как одноэлементный для каждого HttpContext.

public static class YourHtmlHelperExtensionClass 
{
    private class TagSrcAttrTracker
    {
        private TagSrcAttrTracker() { }

        public Dictionary<string, string> sources { get; } = new Dictionary<string, string>();

        public static TagSrcAttrTrackerInstance {
            get {
                IDictionary items = HttpContext.Current.Items;
                const string instanceName = "YourClassInstanceNameMakeSureItIsUniqueInThisDictionary";

                if(!items.Contains(instanceName)) 
                    items[instanceName] = new TagSrcAttrTracker();

                return items[instanceName] as TagSrcAttrTracker;
            }
        }
    }

    public static MvcHtmlString IncludeScriptOnlyOnce(this HtmlHelper helper, string urlOfScript) 
    {
        if(TagSrcAttrTracker.Instance.sources.ContainsKey(urlOfScript))
            return null;

        TagSrcAttrTracker.Instance.sources[urlOfScript] = urlOfScript;

        TagBuilder script = new TagBuilder("script");
        scriptTag.MergeAttribute("src", urlOfScript);

        return MvcHtmlString.Create(script.ToString());
    }
}

Затем разделите JavaScript и другой код на отдельные файлы.

Пример содержимого файла .js

class MyClass{
    myFunction() {
        constructor(divId){
            this._divId = divId
        }

        doSomething() {
            // do something with a div with id == divId
        }
    }
}

Пример содержимого файла .cshtml для частичного просмотра

<link rel="stylesheet" type="text/css" href="~/Content/CustomCSS/MyPartialView.css"/>

<div id="@ViewBag.id">
    Some content!  Yay!
</div>

@Html.IncludeScriptOnlyOnce("/Scripts/CustomScripts/MyPartialView.js")

Пример файла .cshtml, который использует частичное представление

...
<body>
    <h1>Here is some content!</h1>
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_1"} })
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_2"} })
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_3"} })
</body>
<script>
$().ready(
    const id1Functionality = new MyClass("id_1") // forgive the poor naming :-)
    const id2Functionality = new MyClass("id_3")
    const id3Functionality = new MyClass("id_2")

    id1Functionality.doSomething();
    id2Functionality.doSomething();
    id3Functionality.doSomething();
)
</script>

Частичное представление могло быть включено более одного раза, и JavaScript упакован с частичным представлением, но файл .js включается на страницу только один раз, поэтому браузер не жалуется на то, что MyClass был объявлен более одного раза.

person IronMonkey    schedule 28.05.2020

person    schedule
comment
Что вы подразумеваете под последующими рендерами? - person Serge; 05.11.2013
comment
Вызовы @Html.RenderPartial(...) после начального. - person James; 05.11.2013
comment
Но чем поможет скрытое поле? Я не могу прочитать скрытое поле из кода сервера @{ ... }, не так ли? - person Serge; 05.11.2013
comment
@Serge, вам не нужен код на стороне сервера, это проблема view. При первом рендере вставьте скрытое поле (т.е. сначала проверьте его существование с помощью JS, если его нет, запишите его и отрендерите другим JS). Затем при следующем рендеринге частичного представления он найдет скрытое поле, поэтому пропустит все материалы JS. - person James; 05.11.2013
comment
Я не хочу писать все частичные представления (около 40 экземпляров) с полным кодом javascript (словарь JSON длиной около 100 строк), а затем просто отбрасывать все, кроме одного, на стороне клиента. - person Serge; 05.11.2013
comment
@Serge Серж, я не понимаю, зачем тебе нужно писать кучу частей. Смысл частичного представления в том, что его можно повторно использовать, то есть вы пишете его один раз. Если все ваши партиалы различны, я предлагаю переместить общий код в отдельный партиал и вызвать его из ваших более конкретных партиалов. - person James; 05.11.2013
comment
У меня есть 2 партиала (на этом рисунке), которые служат для отображения около 40 элементов на моей странице. Но всем им требовался специальный словарь javascript, логика которого должна полностью обрабатываться частичным представлением (загрузка скрипта на родительской (главной) странице нарушила бы мое правило размещения вещей там, где они принадлежат). - person Serge; 05.11.2013
comment
@Serge, если у каждого частичного есть конкретный И условный JS, то я все равно не вижу, где вам все равно не придется писать эту логику в каждом частичном? Честно говоря, вы не совсем поняли свой вопрос, если вы обновите его образцом кода, возможно, я смогу ответить на него немного лучше. - person James; 05.11.2013
comment
@Serge, так что, глядя на ваше обновление, это вообще не имеет ничего общего с JS - мне кажется, вы хотите отобразить содержимое частичного представления один раз (если только внутри вашего AFieldFormula_DirectFieldFormula.cshtml нет JS, который вы пропустили для краткости? - person James; 05.11.2013
comment
В AFieldFormula_DirectFieldFormula.cshtml есть JS. Для того, чтобы какие-то элементы управления (конкретные кнопки, текстовые поля, ...) работали. Им нужно время от времени собирать данные из словаря JSON (делать это в ajax было бы глупо). Словарь JSON (я называю его JSON, к концу дня это просто данные, доступные из javascript) одинаков для всех элементов управления. - person Serge; 05.11.2013
comment
@Серж, хорошо, я понимаю, что ты сейчас пытаешься сделать. Учитывая, что вам по-прежнему нужно отображать серверный код при каждом вызове, я возвращаюсь к своему исходному решению — использовать для этого чистый JS. Позвольте мне опубликовать пример. - person James; 05.11.2013
comment
Хорошая идея. Но на самом деле мне нужно передать ViewBag Index.cshtml. - person Serge; 05.11.2013
comment
@Serge Серж, нет, вам не нужно, каждое частичное представление получит свое собственное ViewBag - вам нужно только передать одно из ITypedModel, чтобы вы могли сохранять свойство при каждом вызове RenderPartial, вы пробовали решение? - person James; 05.11.2013
comment
Вы забыли, что мне нужен один словарь на всю страницу. И ITypedModel уже несколько раз перерисовывается (модель Index.cshtml — это IEnumerable). - person Serge; 05.11.2013
comment
@Serge, если ITypedModel также визуализируется несколько раз, тогда да, вам нужно будет передать ViewBag из Index.cshtml, хотя это не так уж и сложно, не так ли? - person James; 05.11.2013