I have a View which hold a list of items rendered through partials views.
I'd like to be able in the partial view to add a javascript script only once for the whole page.
I read I could use Context.Items
to share data (like a boolean ScriptAlreadyIncluded
) amongst all (partial) views.
But can I rely on this? Or should I use something else?
My current code (most relevant partial view being ITypedModel.cshtml and 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...
The dictionary would be the same for every AFieldFormula_DirectFieldFormula.cshtml view, so I'd like to compute and display it only once per web page.
Is Context.Items (thread) safe?
No, because HttpContext isn't thread safe, from the docs
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
If you want it to be rendered once you could add a hidden field into the page on the first render and check for the existence of that on subsequent renders.
Based on your comments, you should be able to use the ViewBag
to mainain state across the various partial views and ensure the script is only rendered once
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
}
This will render the script on the first call to RenderPartial("AFieldFormula_DirectFieldFormula")
which then sets the ViewBag.ScriptAlreadyIncluded
property to true
. Then on subsequent renders (as the ViewBag
is being passed in) this property will skip the JS stuff because ScriptAlreadyIncluded
has already been set.