In a previous post, I showed how to edit a variable length list using MvcContrib.FluentHtml. The post was inspired by a post on the same topic by Steve Sanderson. Steve commented on my post pointing out some limitations. To address these limitations required some enhancements to MvcContrib.FluentHtml and some changes to my example. So here we go with take 2.
MvcContrib.FluentHtml Enhancements
The limitations were mostly related to handling validation. To support validation using Model State we have added to MvcContrib.FluentHtml:
- Html Prefix Support
- A Validation Helper
- List Indexing Support
Html Prefix Support
HTML elements generated for strongly typed views have no prefix by default. So elements for ModelViewPage<Person> might look something like this:
<input name="FirstName" id="FirstName" value="Jim" type="text"/>
<input name="LastName" id="LastName" value="Smith" type="text"/>
The problem is, neither MvcContrib’s NameValueSerializer nor MS MVC’s default model binder fully handle this scenario. While NameValueDeserializer does the binding just fine, it does not place bind errors into ModelState. The default model binder does not handle quite handle the binding — it does not deserialize enumerable properties when no prefix is used.
To bind everything correctly and get any errors into ModelState we need a prefix. One way to handle this is to wrap our primary model in a view model. This is a valid pattern. It’s even a standard for some applications. However, we don’t want to have to do this just to get a prefix. So we provide a way to specify a prefix for a strongly typed view.
Either in the code-behind:
public class Index : ModelViewPage<IList<Gift>>
{
public Index() : base("person") { }
}
Or in the view:
<%this.HtmlNamePrefix = "person"%>
Thus our HTML elements will be prefixed:
<input name="person.FirstName" id="person_FirstName" value="Jim" type="text"/>
<input name="person.LastName" id="person_LastName" value="Smith" type="text"/>
Then in our action method, we can use the default binder without any attribute. Any bind errors will be captured in ModelState.
[AcceptPost]
public ViewResult Index(Person person)
New Validation Helper
We have added validation support to FluentHtml for strongly typed views. The following works basically the same as HtmlHelper’s ValidationMessage.
<%this.ValidationMessage(x => x.Price, "Price must be numeric.")%>
We have also added a behavior to ModelViewPage<T>, ModelViewMasterPage<T> and ModelViewUserControl<T> which basically mimics validation used by HtmlHelper. That is, it adds a CSS class “input-validation-error” to any HTML element with an error in ModelState. If you wish, you can remove this behavior or change the CSS class name in the derived class.
List Indexing Support
MS MVC uses a special technique to deserialize enumerable properties. With this technique you set an arbitrary value in a specially named hidden element to signify that a group of elements belongs to a particular instance of the enumerable property. This has an advantage over using positional indexes in that it preserves the identity of an instance across posts, which is necessary for ModelState based validation to work properly.
Therefore, we have added support for this to MvcContrib.FluentHtml. Let’s assume ModelViewPage<IList<Person>> with a prefix of “persons”. Then this markup:
<%var id = Model[i].Id;%>
<%=this.Index(x => x).Value(id)%>
<%=this.TextBox(x => x[id].FirstName).Value(Model[i].FirstName)%>
Will generate HTML like this:
<input id="persons_Index_123" name="persons.Index" value="123" type="hidden"/>
<input id="persons[123]_FirstName" name="persons[123].FirstName" value="Jim" type="text"/>
Changes To Our Example
So based on these changes to FluentHtml we made a few changes to the “Gift Request Form” demo from the previous post. In the main view:
<%for (var i = 0; i < ViewData.Model.Count; i++) {%>
<%ViewData["index"] = i;
Html.RenderPartial("GiftLineItem");%>
<%}%>
And in the user control:
<%var i = (int)ViewData["index"];
var gift = ViewModel == null ? null : ViewModel[i];
var id = gift == null ? -1 * new Random().Next() : gift.Id;
var name = gift == null ? null : gift.Name;
var price = gift == null ? (decimal?)null : gift.Price;%>
<div class="giftLineItem">
<%=this.Index(x => x, x => x[i].Id)%>
<%=this.Hidden(x => x[id].Id).Value(id)%>
<%=this.TextBox(x => x[id].Name).Value(name).Label("Name of gift:")%>
<%=this.TextBox(x => x[id].Price).Value(price).Label("Price ($):")%>
<%=this.ValidationMessage(x => x[id].Price, "Must be a number")%>
<a href="" class="removeGift">Delete</a>
</div>
Viola, validation works:

Note that we stole Steve’s technique of using random negative Ids for unsaved instances, which we will handle in the save method on the server.
GET THE REVISED CODE
GET THE REVISED CODE, UPDATED FOR MVC 2, SIMPLIFIED, AND USING GUID IDs (2/15/10)