Steve Sanderson recently showed a nifty way to edit variable length lists with MS MVC . I commented that I liked his approach. I also mentioned that MvcContrib.FluentHtml might be used to improve a bit of awkward code used to build HTML element names. To illustrate my point I offered some code examples. Steve responded by pointing out the shortcomings of my examples. He was correct. I had not really thought it through. While my examples illustrated the use of FluentHtml, they missed the mark in terms of showing a real solution.
Still I knew that the basic idea of was sound. This article shows in detail how to use MvcContrib.FluentHtml to edit a variable length list of items.
Get The Code
I will use the same basic application as Steve did — a simple gift request form.
Setup
We start with the Gift entity:
public class Gift
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Render The View
Our view derives from MvcContrib.FluentHtml.ModelViewPage<IList<Gift>> and is rendered via a controller action:
[AcceptGet]
public ViewResult Index()
{
ViewData.Model = new List<Gift>
{
new Gift {Id = 1, Name = "Tar Tinker", Price = 23.44m},
new Gift {Id = 2, Name = "Flu Flooper", Price = 11.42m}
};
return View();
}
The view presents a list of the gifts:
<form action="<%=Html.BuildUrlFromExpression<HomeController>(x => x.Index(null))%>" method="post">
<div id="addGiftItem">
<a href="" id="addGift">Add</a>
</div>
<fieldset id="giftLineItems">
<legend>Gifts</legend>
<%for (var i = 0; i < ViewData.Model.Count; i++) {%>
<%Html.RenderPartial("GiftLineItem", ViewData.Model, new ViewDataDictionary {{"index", i}});%>
<%}%>
</fieldset>
<%=this.SubmitButton("Save")%>
</form>
GiftLineItem is a user control that derives from MvcContrib.FluentHtml.ViewModelUserControl<IList<Gift>>:
<%var i = (int)ViewData["index"];%>
<div class="giftLineItem">
<%=this.Hidden(x => x[i].Id)%>
<%=this.TextBox(x => x[i].Name).Label("Name of gift:")%>
<%=this.TextBox(x => x[i].Price).Label("Price ($):")%>
<a href="" class="removeGift">Delete</a>
</div>
You might observe that this user control uses IList<Gift> as its model whereas Steve’s uses Gift. But the user control represents one gift, right? Have I sacrificed cohesion? Yes, however a closer look shows that neither Steve’s user control nor mine is cohesive. That is, neither stands alone outside of the context of a list. We simply use different techniques to name the elements within the context of an owning list.
Here’s the rendered view (after clicking “Add” twice):

Add Behavior
Now we need to add behavior to the “Add” and “Delete” links. For that we’ll use jQuery:
$(document).ready(function() {
var nextGiftIndex = <%=ViewData.Model.Count%>;
setRemoveLinks();
$('#addGift').click(function() {
$.get('/Home/AddGift?index=' + nextGiftIndex, 'html', function(lineItemHtml) {
$('#giftLineItems').append(lineItemHtml);
setRemoveLinks();
});
nextGiftIndex++;
return false;
});
});
function setRemoveLinks() {
$('a.removeGift').click(function() {
$(this).parent('div').remove();
return false;
});
}
Clicking the “Add” link calls the AddGift action asyncronously and passes the index of the item to be added. The index value is initialized when the page is rendered and incremented on the client side with each addition. The “Delete” links are wired up to a simple function that removes the parent div.
The AddGift action simply renders the user control using the specified index:
public ViewResult AddGift(int index)
{
ViewData["index"] = index;
return View("~/Views/Home/GiftLineItem.ascx");
}
Notice we have not specified a model for the user control, which means it will be null. As a result, the name and price textboxes will be blank, which is what we want. And by specifying the desired index, these textboxes will be named such that they will bind correctly when form is saved.
Save Changes
Clicking the “Save” button posts the form to the following action:
[AcceptPost]
public ViewResult Index([Deserialize]IList<Gift> gifts)
{
//code to save the changes
ViewData.Model = gifts;
return View();
}
Notice that the parameter “gifts” is decorated with the Deserialize attribute. This attribute uses MvcContrib’s NameValueDeserializer to obtain our collection of gifts from the form post. It does not require that collections be indexed purely in sequence. Thus it handles any holes left by deleted line items.
(NOTE: I expected that using [Bind(Prefix = "")] would work the same as [Deserialize], but it does not. Seems to be a limitation.)
Conclusion
The solution presented here differs from Steve’s in that it uses FluentHtml. There are a few other differences, such as jQuery versus MS Ajax. My point is not present a solution that is superior, rather just to show how it might be done using FluentHtml and expressions to avoid some fiddly construction of HTML element names in views.