Tim Scott's Blog

January 13, 2009

Editing a Variable Length List Of Items With MvcContrib.FluentHtml – Take 2

Filed under: FluentHtml, MS MVC — Tim Scott @ 11:48 pm

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:

showvalidationerror1

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)

About these ads

6 Comments »

  1. I just checked out FluentHtml today and I must say one of the best days coding I have had in a long time :) I love the behaviours that you can setup and will make setting up jQuery a breeze. Also the Select with enum is sick – one line of code and your done!

    Cheers heaps
    Jake

    Comment by Jake Scott — January 21, 2009 @ 10:37 am

  2. [...] Editing a Variable Length List Of Items With MvcContrib.FluentHtml – Take 2 [...]

    Pingback by ASP.NET MVC View Best Practices – Save time creating views with MvcContrib.FluentHtml | Arrange Act Assert — October 19, 2009 @ 11:25 am

  3. [...] Validation of Variable Length Lists – Tim Scott [...]

    Pingback by Dev Links « Blogosphere — December 27, 2009 @ 1:19 am

  4. [...] about a way of editing a list of items where the user can add or remove as many items as they want. Tim Scott later provided some helpers to make the code neater. Now, I find myself making use of this technique so often that [...]

    Pingback by Editing a variable length list, ASP.NET MVC 2-style « Steve Sanderson’s blog — January 28, 2010 @ 3:02 pm

  5. ” Any bind errors will be captured in ModelState.”
    You can read more?

    Comment by ramtori — December 20, 2010 @ 9:41 pm

  6. [...] about a way of editing a list of items where the user can add or remove as many items as they want. Tim Scott later provided some helpers to make the code neater. Now, I find myself making use of this technique so often that [...]

    Pingback by Editing a variable length list, ASP.NET MVC 2-style | Flyfox's Blog — May 2, 2011 @ 8:30 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: