For development speed I didn't want to introduce the complexity and overhead of Angular 1.x or 2.x, and I didn't want to lose the strong typing and unobtrusive client side validation that .NET MVC provides. While Angular has its place, it feels like overkill to need another client side framework to edit a few collections in a form. To keep complexity low I wanted to find a pattern that used as little java-script as possible and would be usable throughout the application.
After digging into the issue I found several blog posts on the topic, among those the two below explain the principles of how this works.
Phil Hack - 2008 - Model Binding To A List
Ivan Zlatev - 2011 Series - Editing Variable Length Reorderable Collections in ASP.NET MVC – Part 1: ASP.NET MVC Views
The heart of the issue revolves around MVC model binding. The default model binding uses the object and property name as the html id and name attribute. For example, say we have an object called Asset with a property Url that is placed into our view using the EditorFor Html helper.
This would result in raw HTML that looks like this...
<div class="form-group"> <label class="control-label col-md-2" for="Asset_Url">URL *</label> <div class="col-md-10"> <input class="form-control text-box single-line" data-val="true" data-val-required="The URL * field is required." id="Asset_Url" name="Asset.Url" type="text" value="~/Content/DealerContent/23138/site.css"> <span class="field-validation-valid text-danger" data-valmsg-for="Asset.Url" data-valmsg-replace="true"></span> </div> </div>
Notice the id, for, name, data-val-required and data-valmsg-for all our object name and property in them. Also be aware that using EditorTemplates and nesting your model through the templates will affect this naming scheme. For example in another of my apps I have an id that looks like this, "Client_ClientSecrets_0__Description". These are the attributes that MVC model binding and unobtrusive client side validation tie into. If you write out a collection you'll notice that an index position gets added into the attribute.
<div class="col-md-10"> <input class="form-control text-box single-line" data-val="true" data-val-required="The URL * field is required." id="Assets_0__Url" name="Assets.Url" type="text" value="~/Content/DealerContent/23138/site.css"> <span class="field-validation-valid text-danger" data-valmsg-for="Asset.Url" data-valmsg-replace="true"></span> </div>
MVC model binding knows you have a collection when it picks up the index in the html attribute names. It's smart, but finicky. If your indexes get out of sequence when your form posts you'll find collections that are missing items.
To recap and boil the problem down, when editing collections client side the html input elements must be formatted as the model binder expects, and the collection index must stay in order.
As a side note, we are also interested in keeping the unobtrusive client side validation working. The wiring for validation runs after the page loads, therefor items added dynamically into the page are unknown by by client side validation. To get around this we'll add in little extra jQuery magic. Now onto the code.