Here is my working attempt to allow multiple/infinite child record entries with the original parent post in .NET MVC. Any refactoring to simplify my code would be greatly appreciated:
Controller:
public ActionResult Create()
{
EquipmentLicense equipmentlicense = new EquipmentLicense();
equipmentlicense.EquipmentLicenseKeys = new List<mySolution.Models.EquipmentLicenseKey>() { new EquipmentLicenseKey() };
return View(equipmentlicense);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EquipmentLicenseId,ProductName")] EquipmentLicense EquipmentLicense, FormCollection formCollection)
{
try
{
if (ModelState.IsValid)
{
EquipmentLicense.EquipmentLicenseId = Guid.NewGuid();
db.EquipmentLicenses.Add(EquipmentLicense);
foreach (var formcollectionkey in formCollection.AllKeys)
{
int intCollectionLength = formcollectionkey.Length;
if (intCollectionLength == 68)
{
if (formcollectionkey.Substring(58).ToString() == "LicenseKey")
{
if (formCollection[formcollectionkey].Trim().Length > 0)
{
EquipmentLicenseKey newEquipmentLicenseKey = new EquipmentLicenseKey();
string stringLicenseKey = formCollection[formcollectionkey];
string guidEquipmentLicenseKeyId = formcollectionkey;
guidEquipmentLicenseKeyId = guidEquipmentLicenseKeyId.Remove(guidEquipmentLicenseKeyId.Length - 12, 12);
guidEquipmentLicenseKeyId = guidEquipmentLicenseKeyId.Remove(0, 20);
newEquipmentLicenseKey.EquipmentLicenseKeyId = Guid.Parse(guidEquipmentLicenseKeyId);
newEquipmentLicenseKey.EquipmentLicenseId = EquipmentLicense.EquipmentLicenseId;
newEquipmentLicenseKey.LicenseKey = stringLicenseKey;
db.EquipmentLicenseKeys.Add(newEquipmentLicenseKey);
}
}
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch
{
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
return View(EquipmentLicense);
}
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult LicenseKeyEntryRow()
{
return PartialView("LicenseKeyEntryEditor");
}
Infrastructure:
public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html, string collectionName)
{
if (String.IsNullOrEmpty(collectionName))
{
throw new ArgumentException("collectionName is null or empty.", "collectionName");
}
string strGuid = Guid.NewGuid().ToString();
string collectionIndexFieldName = String.Format("{0}.Index", collectionName);
string itemIndex = null;
if (html.ViewData.ContainsKey(JQueryTemplatingEnabledKey))
{
itemIndex = "${index}";
}
else
{
itemIndex = GetCollectionItemIndex(collectionIndexFieldName, strGuid);
}
string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);
TagBuilder indexField = new TagBuilder("input");
indexField.MergeAttributes(new Dictionary<string, string>() {
{ "name", collectionIndexFieldName },
{ "value", itemIndex },
{ "type", "hidden" },
{ "autocomplete", "off" }
});
html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}
private const string JQueryTemplatingEnabledKey = "__BeginCollectionItem_jQuery";
public static MvcHtmlString CollectionItemJQueryTemplate<TModel, TCollectionItem>(this HtmlHelper<TModel> html, string partialViewName, TCollectionItem modelDefaultValues)
{
ViewDataDictionary<TCollectionItem> viewData = new ViewDataDictionary<TCollectionItem>(modelDefaultValues);
viewData.Add(JQueryTemplatingEnabledKey, true);
return html.Partial(partialViewName, modelDefaultValues, viewData);
}
private static string GetCollectionItemIndex(string collectionIndexFieldName, string strGuid)
{
Queue<string> previousIndices = (Queue<string>)HttpContext.Current.Items[collectionIndexFieldName];
if (previousIndices == null)
{
HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();
string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
if (!String.IsNullOrWhiteSpace(previousIndicesValues))
{
foreach (string index in previousIndicesValues.Split(','))
{
previousIndices.Enqueue(index);
}
}
}
return previousIndices.Count > 0 ? previousIndices.Dequeue() : strGuid;
}
private class CollectionItemNamePrefixScope : IDisposable
{
private readonly TemplateInfo _templateInfo;
private readonly string _previousPrefix;
public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
{
this._templateInfo = templateInfo;
_previousPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = collectionItemName;
}
public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousPrefix;
}
}
Create.cshtml:
@model mySolution.Models.EquipmentLicense
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-horizontal">
<div class="row">
<div class="col-md-4">
<div class="form-group">
@Html.LabelFor(model => model.ProductName, htmlAttributes: new { @class = "control-label " })
<div class="">
@Html.EditorFor(model => model.ProductName, new { htmlAttributes = new { @class = "form-control", width = "100&", style = "max-width:100&" } })
@Html.ValidationMessageFor(model => model.ProductName, "", new { @class = "text-danger" })
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
@Html.LabelFor(model => model.EquipmentLicenseKeys.FirstOrDefault().LicenseKey, htmlAttributes: new { @class = "control-label " })
<div class="row">
<div class="col-md-1">
<a id="addLicenseKey" href="#" class="btn btn-primary btn-md">Add <span class="glyphicon glyphicon-plus"></span></a>
</div>
<div class="col-md-11">
<ul id="LicenseKeyEditor" style="list-style-type: none">
@if (Model.EquipmentLicenseKeys != null)
{
foreach (EquipmentLicenseKey equipmentlicensekey in Model.EquipmentLicenseKeys)
{
Html.RenderPartial("LicenseKeyEntryEditor", equipmentlicensekey);
}
}
</ul>
</div>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="form-group" >
<div class="col-md-1 col-md-offset-4">
<a href="@Url.Action("Index", "License")" class="btn btn-default pull-left">Cancel <span class="glyphicon glyphicon-remove"></span></a>
</div>
<div class="col-md-1 col-md-offset-2">
<button type="submit" value="Create License" class="btn btn-primary pull-right">Submit <span class="glyphicon glyphicon-floppy-disk"></span></button>
</div>
</div>
</div>
</div>
}
@section Scripts {
@Styles.Render("~/Content/css")
@Styles.Render("~/Content/jquery-theme")
@Scripts.Render("~/bundles/jquery")
@Styles.Render("~/Content/bootstrap")
@Scripts.Render("~/bundles/bootstrap")
<script type="text/javascript" language="javascript">
$(document).ready(function () {
$("#LicenseKeyEditor").sortable();
$("#addLicenseKey").click(function () {
$.get('/License/LicenseKeyEntryRow', function (template)
{
$("#LicenseKeyEditor").append(template);
});
});
})
</script>
}
LicenseKeyEntryEditor.cshtml:
@model mySolution.Models.EquipmentLicenseKey
@using mySolution.Infrastructure;
<li>
@using (Html.BeginCollectionItem("EquipmentLicenseKey"))
{
<div class="row">
<div class="col-md-11">
<div class="form-group">
<div class="">
@Html.EditorFor(m => m.LicenseKey, new { htmlAttributes = new { @class = "form-control", width = "100&", style = "max-width:100&" } })
@Html.ValidationMessageFor(model => model.LicenseKey, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="col-md-1">
<a href="#" class="btn btn-primary btm-sm" onclick="$(this).parent().parent().remove();">
Delete <span class="glyphicon glyphicon-trash"></span>
</a>
</div>
</div>
}
</li>
<code>
tags along with<
and>
when posting code here. Use indentation only. \$\endgroup\$ – Jamal♦ Feb 12 '17 at 22:28