My Take On Variable Length Lists in ASP.NET MVC

Catching Up


If you haven't already and want an understanding of the underlying issues at hand I recommend reading Phil Haack's and Steve Sanderson's posts.  To sum up, doing variable length lists in ASP.NET MVC is currently a fairly ugly process for some rather good reasons, and I wanted to see if I could take a stab at simplifying it.

Getting Started

I had previously written my own custom TextBoxFor<T> extensions.  In them, I traversed the expression tree to generate an input name that would look something like this with a ViewPage<Person>:

    <%= Html.TextBoxFor(p=>p.FirstName) %>

and would output:

    <input type="text" value="Justin" name="person.FirstName" />


The benefits of this method is that you can traverse an arbitrary length object graph and have it output html that will automatically work with ASP.NET MVC's default model binding.  For example (if a poor one):

    <%= Html.TextBoxFor(p=>p.Address.State.Country) %>

which would produce:

    <input type="text" value="United States" name="person.Address.State.Country" />


and we could then just get the entire Person back by simply defining a method on our controller like so:

            public ActionResult SubmitPerson(Person person)

            {

                //do stuff

            }


Inspiration

Now I was trying to think of a way to clean up the implementations that Phill and Steve used and was struck with one of those ah ha moments.  Having previously been looking at an excellent, up-and-coming patterns framework for dataccess, validation, and business rules by Ritesh Rao called NCommon.  I realized I could use the same pattern that he uses to implement his UnitOfWorkScope.  The end result of it all is this:

With ViewPage<IList<Person>>

        <%foreach (Person p in Model){%>

            <%using(Html.CollectionScope()){ %>

                <%Html.RenderPartial("~/Views/List/PersonControl.ascx", p); %>

            <%} %>

        <%}%>


and inside the person user control: 

         <%= Html.HiddenFor(p=>p.Id) %>

         <%= Html.TextBoxFor(p=>p.FirstName) %>

         <%= Html.TextBoxFor(p=>p.LastName) %>

         <%foreach (Address address in Model.Addresses){%>

             <%using(Html.CollectionScope()){ %>

                <% Html.RenderPartial("~/Views/List/AddressControl.ascx", address); %>

             <%} %>

         <%} %>


and with a little extension method foo we can get to this:

     <% Model.HtmlEach(p => Html.RenderPartial("~/Views/List/PersonControl.ascx", p));%>


And this small amount of display logic allows us to automatically get our list of Persons back like so:

            public ActionResult SubmitPeople(IList<Person> people)

            {

                //do stuff

            }


The Code

If you want to just jump right in and get the code you can access it here.  Otherwise read on.

Edit: the above download is an updated copy of the code as described here.

The CollectionScope class that I use here:

        <% using(Html.CollectionScope()){ %>

   

        <%} %>

 
is stored on a Stack<CollectionScope> in HttpContext.Current.Items.  This allows us to keep track of all of our CollectionScopes and generate our object graph with or without indexers, based upon whether we are within a CollectionScope or not.

The implementation of our Html extension Method TextBoxFor<T> is where we access the CollectionScope stack and generate our input names.

   28         public static string TextBoxFor<T>(this VariableList.HtmlHelper<T> helper, Expression<Func<T, string>> action, object htmlAttributes) where T : class

   29         {

   30             var body = action.Body as MemberExpression;

   31             if(body != null)

   32             {

   33                 string name = body.GetObjectGraph();

   34                 var function = action.Compile();

   35                 T model = (T)helper.ViewData.Model;

   36                 string value = function(model);

   37 

   38                 if (CollectionScope.Current != null &&   !CollectionScope.Current.HasSetIndexer)

   39                 {

   40                     CollectionScope.Current.HasSetIndexer = true;

   41 

   42                     string indexProp = name.Substring(0, name.LastIndexOf('['));

   43                     return helper.Hidden(indexProp + ".Index",

   44                         CollectionScope.Current.Indexer.ToString())

   45                         + helper.TextBox(name, value, htmlAttributes);

   46                 }

   47 

   48                 return helper.TextBox(name, value, htmlAttributes);

   49             }

   50 

   51             return string.Empty;

   52         }



The most important line (33) is currently hidden in an extension method .GetObjectGraph().
This extension method basically recurses through the Expression tree and generates the name attribute that we then insert into our input tag.  Here is an example input tag from the demo project.

   <input type="text" value="City0:0" name="people[eb528441-c0b7-46fc-bd41-85dca72a86d6].addresses[2a269c54-1396-4804-a83c-dbde65b19a84].City" id="people[eb528441-c0b7-46fc-bd41-85dca72a86d6].addresses[2a269c54-1396-4804-a83c-dbde65b19a84].City"/>


As you can see I'm using a Guid to ensure uniqueness for each index.  I'm not a huge fan of this and will probably rework it to keep page-size down and readability up but for now it works.

Things I Don't Like

As I stated above I'm using a Guid to ensure uniqueness.  This makes the page a little ugly to read and could hurt performance.  Another issue currently is this block of code in each For<T> extension:

   38                 if (CollectionScope.Current != null && !CollectionScope.Current.HasSetIndexer)

   39                 {

   40                     CollectionScope.Current.HasSetIndexer = true;

   41 

   42                     string indexProp = name.Substring(0, name.LastIndexOf('['));

   43                     return helper.Hidden(indexProp + ".Index",

   44                         CollectionScope.Current.Indexer.ToString())

   45                         + helper.TextBox(name, value, htmlAttributes);

   46                 }


This block is used to insert an input tag to define the indexer for each unique item in a collection.  There are a couple of ways I could rework this to make it a lot less code for each Html extension method but levib mentions in this thread that the requirement for the indexer definition is likely to go away in the next drop of ASP.NET MVC, so I'm not going to bother at the moment.

The Sappy Ending

Since this is my first post I'd like to say a big "Thank You!" to all the bloggers out there that helped me on my way.  If you have any questions, comments, or constructive criticism please feel free to leave them in the comments.

Edit:  An updated post on this topic can be found here.

posted @ Wednesday, January 07, 2009 12:06 AM

Print

Comments on this entry:

# re: My Take On Variable Length Lists in ASP.NET MVC

Left by Steve Sanderson at 1/7/2009 2:49 AM
Gravatar
Hey, nice one! Looks like there are some great ideas in your code.

Have you considered using Html.ValidationMessage() to display details of any binding errors that occurred? To make this work, you might need to stop using random GUID keys and ensure each entity keeps using the same key on subsequent requests.

Your CollectionScope concept is very neat. What about changing it to something like "ControlPrefixScope" so that it simply builds up a stack of HTML ID prefixes to keep things unique? That way, the programmer would remain in control of those prefixes and could ensure they are tied to the identity of entities being rendered.

# re: My Take On Variable Length Lists in ASP.NET MVC

Left by justin greene at 1/7/2009 7:49 AM
Gravatar
Hi Steve, thanks for stopping by!

I actually have put a little bit of thought into the validation issue, and was thinking of just using a convention. If the model has a property "Id" it will use that as the index. Another option is just having an overloaded constructor that supplies an index, i.e. Html.CollectionScope("1").

On another note, I was struggling with the name for the "CollectionScope" class and gave up and just called it CollectionScope. I think I like your idea for ControlPrefixScope better, I'll update that when I get a chance.

# My Take On Variable Length Lists in ASP.NET MVC

Left by Web Development Community at 1/7/2009 11:30 AM
Gravatar
You are voted (great) - Trackback from Web Development Community

# My Take On Variable Length Lists in ASP.NET MVC

Left by DotNetShoutout at 1/7/2009 2:37 PM
Gravatar
Thank you for submitting this cool story - Trackback from DotNetShoutout

# My Take On Variable Length Lists in ASP.NET MVC

Gravatar
You have been linked! DotNetLinks.net

# re: My Take On Variable Length Lists in ASP.NET MVC

Left by Shiju Varghese at 1/8/2009 12:40 AM
Gravatar
Hi, very nice and cool.

# re: My Take On Variable Length Lists in ASP.NET MVC

Left by Eddie Garmon at 1/8/2009 8:57 AM
Gravatar
Instead of using random guids keys, why not use the hash code from the object? The only issue with this is that alot of people use the default implementation based on memory location. Otherwise, the code would be like:

<%foreach (Address address in Model.Addresses){%>
<%using(Html.CollectionScope(address.GetHashCode())){ %>
...

Smaller output, and you can reconnect to the origional object without having to necessarily supply a hidden id field.

# re: My Take On Variable Length Lists in ASP.NET MVC

Left by David Alpert at 1/8/2009 11:50 AM
Gravatar
thanks for sharing great work on a real problem.

I havn't tackled this one yet but it has surely been on my mind as the MVC application i'm working on will absolutely need to bind lists of ojbects in order to be practical.

Thank you for making it easier!

# 

Left by Blog of Developer Mikkel Ovesen at 1/10/2009 6:38 AM
Gravatar
ASP.NET MVC Variable length lists

# ����� �����: �����

Gravatar
&lt;a href=&quot;www.feiki-moskvi.ru/...vost.html&quot;&gt;����� �����: �����&lt;/a&gt;

# �������� ������� �����

Gravatar
&lt;a href=&quot;www.feiki-moskvi.ru/...kaya.html&quot;&gt;�������� ������� �����&lt;/a&gt;

# ������� �������������

Gravatar
&lt;a href=&quot;www.mos-med-konsult.ru/...y.html&quot;&gt;������� �������������&lt;/a&gt;

# ������ ���� ����

Gravatar
&lt;a href=&quot;www.moscow-intim2009.ru/....html&quot;&gt;������ ���� ����&lt;/a&gt;

# ����� ������� ����������� �����

Gravatar
&lt;a href=&quot;www.devchonok-da.ru/...utki.html&quot;&gt;����� ������� ����������� �����&lt;/a&gt;

# ����� ������� �����

Gravatar
&lt;a href=&quot;www.devchonok-da.ru/...luhi.html&quot;&gt;����� ������� �����&lt;/a&gt;

# ����������� �����

Gravatar
&lt;a href=&quot;www.devchonok-da.ru/...ideo.html&quot;&gt;����������� �����&lt;/a&gt;

# ������� ���������

Gravatar
&lt;a href=&quot;www.remontiruem77.ru/...add.html&quot;&gt;������� ���������&lt;/a&gt;

# �������� ���� ���������

Gravatar
&lt;a href=&quot;www.remontiruem77.ru/...lok.html&quot;&gt;�������� ���� ���������&lt;/a&gt;

# ���������� ������ �������������

Gravatar
&lt;a href=&quot;www.remontiruem77.ru/...dov.html&quot;&gt;���������� ������ �������������&lt;/a&gt;

# ��������� ���������� ������

Gravatar
&lt;a href=&quot;www.remontiruem77.ru/...dov.html&quot;&gt;��������� ���������� ������&lt;/a&gt;

# ������������� ������� �����������

Gravatar
&lt;a href=&quot;www.remontiruem77.ru/...ika.html&quot;&gt;������������� ������� �����������&lt;/a&gt;

# ���� ���������� ������ ����������

Gravatar
&lt;a href=&quot;www.devichnik-tut.ru/...uhi.html&quot;&gt;���� ���������� ������ ����������&lt;/a&gt;

# ����������� ����� ��������

Gravatar
&lt;a href=&quot;www.devichnik-tut.ru/...dey.html&quot;&gt;����������� ����� ��������&lt;/a&gt;

# ����������� � ������

Gravatar
&lt;a href=&quot;www.devichnik-tut.ru/...kva.html&quot;&gt;����������� � ������&lt;/a&gt;

# ���� ����������� �������

Gravatar
&lt;a href=&quot;www.devichnik-tut.ru/...aya.html&quot;&gt;���� ����������� �������&lt;/a&gt;

# ����� ������� �����

Gravatar
&lt;a href=&quot;www.nu-gde-devchonki.ru/....html&quot;&gt;����� ������� �����&lt;/a&gt;

# ����� ����� ������

Gravatar
&lt;a href=&quot;www.nu-gde-devchonki.ru/....html&quot;&gt;����� ����� ������&lt;/a&gt;

# �������������� �����

Gravatar
&lt;a href=&quot;www.nu-gde-devchonki.ru/....html&quot;&gt;�������������� �����&lt;/a&gt;

# ����������� ������������� ������������� ����

Gravatar
&lt;a href=&quot;www.chudesnie-devki.ru/...g.html&quot;&gt;����������� ������������� ������������� ����&lt;/a&gt;

# �����-�

Left by �����-� at 4/12/2009 4:10 PM
Gravatar
&lt;a href=&quot;www.kino-skach.ru/revansh.html&quot;&gt;�����-�&lt;/a&gt;

# ����������� �����

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...birsk.html&quot;&gt;����������� �����&lt;/a&gt;

# ����� ����� ����

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...hluhi.html&quot;&gt;����� ����� ����&lt;/a&gt;

# ������ ����� ����������� ������������

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...hluhi.html&quot;&gt;������ ����� ����������� ������������&lt;/a&gt;

# ����������� ������

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...hluhi.html&quot;&gt;����������� ������&lt;/a&gt;

# ����������� ������ ������

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...shevo.html&quot;&gt;����������� ������ ������&lt;/a&gt;

# ������ ����� �����

Gravatar
&lt;a href=&quot;www.feiki-intim.ru/...miass.html&quot;&gt;������ ����� �����&lt;/a&gt;

# ������� ����������� ������

Gravatar
&lt;a href=&quot;www.prostitutkam-da.ru/...k.html&quot;&gt;������� ����������� ������&lt;/a&gt;

# ����������� ������������ ����� ������ ������

Gravatar
&lt;a href=&quot;www.prostitutkam-da.ru/...r.html&quot;&gt;����������� ������������ ����� ������ ������&lt;/a&gt;

# ���� ���� ������

Left by ���� ���� ������ at 4/17/2009 10:34 PM
Gravatar
&lt;a href=&quot;www.prostitutkam-da.ru/shluhi-ru.html&quot;...���� ���� ������&lt;/a&gt;

# ��� ����� ������

Left by ��� ����� ������ at 4/18/2009 10:49 PM
Gravatar
&lt;a href=&quot;www.devochki-feiki.ru/...va.html&quot;&gt;��� ����� ������&lt;/a&gt;

# ����� ��� ������

Gravatar
&lt;a href=&quot;www.devochki-feiki.ru/...ki.html&quot;&gt;����� ��� ������&lt;/a&gt;

# ����������� � ��������

Gravatar
&lt;a href=&quot;www.devochki-feiki.ru/...ra.html&quot;&gt;����������� � ��������&lt;/a&gt;

# ������� �����

Left by ������� ����� at 4/27/2009 10:09 AM
Gravatar
&lt;a href=&quot;www.intimnie-sekreti.ru/....html&quot;&gt;������� �����&lt;/a&gt;

# ���������� ���� ����� �������

Gravatar
&lt;a href=&quot;www.intimnie-sekreti.ru/....html&quot;&gt;���������� ���� ����� �������&lt;/a&gt;

# ����������� ������ ���������

Gravatar
&lt;a href=&quot;www.intimnie-sekreti.ru/....html&quot;&gt;����������� ������ ���������&lt;/a&gt;

# ����������� ����� ������

Gravatar
&lt;a href=&quot;www.intimnie-sekreti.ru/....html&quot;&gt;����������� ����� ������&lt;/a&gt;

# ����������� ����� ��������

Gravatar
&lt;a href=&quot;www.intimnie-sekreti.ru/....html&quot;&gt;����������� ����� ��������&lt;/a&gt;

# ���� ��� ����� �����������

Gravatar
&lt;a href=&quot;www.znakomstva-2009.ru/...k.html&quot;&gt;���� ��� ����� �����������&lt;/a&gt;

# ����� �����������

Gravatar
&lt;a href=&quot;www.znakomstva-2009.ru/...r.html&quot;&gt;����� �����������&lt;/a&gt;

# �������� ������� �����������

Gravatar
&lt;a href=&quot;www.znakomstva-2009.ru/...i.html&quot;&gt;�������� ������� �����������&lt;/a&gt;

# ����� ���������

Left by ����� ��������� at 4/28/2009 2:30 PM
Gravatar
&lt;a href=&quot;www.znakomstva-2009.ru/...a.html&quot;&gt;����� ���������&lt;/a&gt;

# ������ ����� online ���������

Gravatar
&lt;a href=&quot;www.igraem-v-xoldem.ru/...e.html&quot;&gt;������ ����� online ���������&lt;/a&gt;

# ����� �������

Left by ����� ������� at 6/29/2009 7:18 AM
Gravatar
&lt;a href=&quot;www.igraem-holdem.ru/post2-ka.html&quot;&am...����� �������&lt;/a&gt;

# ��������� ������

Gravatar
&lt;a href=&quot;www.igraem-holdem.ru/post109-om.html&quot;&...��������� ������&lt;/a&gt;

# ������� ����� ����� ����

Gravatar
&lt;a href=&quot;www.igraem-holdem.ru/post913-ce.html&quot;&...������� ����� ����� ����&lt;/a&gt;

# ����� ������ ����� �� �����������

Gravatar
&lt;a href=&quot;www.igraem-holdem.ru/...-ck.html&quot;&gt;����� ������ ����� �� �����������&lt;/a&gt;

# �������� � ����� ������

Gravatar
&lt;a href=&quot;www.igraem-holdem.ru/...-oi.html&quot;&gt;�������� � ����� ������&lt;/a&gt;

Your comment:



 (will not be displayed)


 
 
 
Please add 8 and 5 and type the answer here:
 

Live Comment Preview:

 
«July»
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678