对于所有MVC局部视图只执行一次:Context.Items(线程)是否安全?

时间:2013-11-05 10:39:28

标签: c# asp.net-mvc-5

我有一个视图,其中包含通过局部视图呈现的项目列表。

我希望能够在局部视图中为整个页面添加一次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视图的字典都是相同的,所以我想计算每个网页只显示一次。

4 个答案:

答案 0 :(得分:2)

  

Context.Items(线程)安全吗?

不,因为HttpContext不是线程安全的,来自文档

  

此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。

如果您希望在第一次渲染时在页面中添加隐藏字段并在后续渲染中检查是否存在,则需要渲染它。


根据您的评论,您应该能够在各个部分视图中使用ViewBag来维护状态并确保脚本仅呈现一次

<强> 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, new ViewDataDictionary { { "vb", ViewBag }})
        }
    </span>
}

<强> AFieldFormula_DirectFieldFormula.cshtml

@model MygLogWeb.Classes.DirectFieldFormula

@{ var vb = ((dynamic)ViewData["vb"]); }

@if (!vb.ScriptAlreadyIncluded)
{
    ...Build a JSON dictionnary...   
}

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

@if (!vb.ScriptAlreadyIncluded)
{
    ...Some controls using the JSON dictionnary...
}

@if (!vb.ScriptAlreadyIncluded)
{
    @{ vb.ScriptAlreadyIncluded = true; } // set the flag
}

这将在第一次调用RenderPartial("AFieldFormula_DirectFieldFormula")时呈现脚本,然后将ViewBag.ScriptAlreadyIncluded属性设置为true。然后在后续渲染中(当传递ViewBag时),此属性将跳过JS内容,因为ScriptAlreadyIncluded已经设置。

答案 1 :(得分:1)

  

Context.Items(线程)安全吗?

不,如James says in his answer

但它需要吗?如果您没有进行任何异步处理,那么您的请求将在同一个线程上执行,因此线程安全不是问题。

Context.Items旨在在请求处理管道的不同部分之间共享对象。

答案 2 :(得分:0)

基于@James的想法和this post

我可以使用:ViewContext.Controller.ViewBag,因为整个页面只有一个Controller(我不在那里使用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;
}

...

答案 3 :(得分:0)

所以,这是一个古老的问题,但是我在尝试解决最近的问题时发现了它。

根据Microsoft,HttpContext不是线程安全的。好消息是,有一种方法可以实现可重用的解决方案,该解决方案只能按照Microsoft的建议在ASP.NET控制的线程中使用HttpContext。主要限制是此方法只能跟踪使用该方法添加的脚本,但是专门为向部分视图添加功能而创建的脚本无论如何都应仅与部分视图一起使用。见下文。

Here是我留下此答案的原始位置。它利用HttpContext.Current.Items来维护每个请求的单例静态类,其中包含已包含脚本的字典(使用此方法)。

实施Html帮助程序扩展功能,该功能只会加载一次脚本。 (这也适用于CSS和其他随附文件)。

在HttpContext中,将HtmlHelper类的扩展和私有支持类作为单例来实现。

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被声明了多次。