5

I'm following the info on http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx from Phil Haack

He talks about Non-Sequential indices:

<form method="post" action="/Home/Create">

<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />

<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />

<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />

<input type="submit" />
</form>

Is this possible in MVC3 when you use model binding with TextBoxFor?
This is the way to do it with sequentiel indices:

@Html.TextBoxFor(m => m[i].Value)

If it's not possible, is their anything else I can do if my indices will not be sequential?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Kevin
  • 125
  • 1
  • 10
  • This is still a sequential list of items. You need to provide more context as to what you are trying to achieve beyond just generating the alternative indexing HTML. Are you trying to implement collection item reordering,addition and removal else you don't need this syntax? – Ivan Zlatev Jun 04 '11 at 19:47

3 Answers3

1

AFAI understand the non-sequential indices technique would require to add hidden fields for each index value. I'm quite sure that the Html.TextBoxFor helper itself does not generate any additional hidden fields. Probably you could achieve this by manually adding the hidden fields with the non-sequential indices.

Tz_
  • 2,949
  • 18
  • 13
0

To not messing with your view, the easiest way I found to do this is following this extension: BeginCollectionItem.

The complete project is here: https://github.com/danludwig/BeginCollectionItem

But AFAIK you only need this class:

public static class HtmlPrefixScopeExtensions
    {
        private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

        public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
        {
            var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
            string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

            // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
            html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

            return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
        }

        public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
        {
            return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
        }

        private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
        {
            // We need to use the same sequence of IDs following a server-side validation failure,  
            // otherwise the framework won't render the validation error messages next to each item.
            string key = idsToReuseKey + collectionName;
            var queue = (Queue<string>)httpContext.Items[key];
            if (queue == null) {
                httpContext.Items[key] = queue = new Queue<string>();
                var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
                if (!string.IsNullOrEmpty(previouslyUsedIds))
                    foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                        queue.Enqueue(previouslyUsedId);
            }
            return queue;
        }

        private class HtmlFieldPrefixScope : IDisposable
        {
            private readonly TemplateInfo templateInfo;
            private readonly string previousHtmlFieldPrefix;

            public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
            {
                this.templateInfo = templateInfo;

                previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
                templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
            }

            public void Dispose()
            {
                templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
            }
        }
    }

How to use it in your Views:

<form method="post" action="/Home/Create">
    @foreach (var item in Model.Products) {    
        @using (Html.BeginCollectionItem("Products"))
        { 
            @Html.TextBoxFor(item => item.Name)
            @Html.TextBoxFor(item => item.Price)            
        }
     } 
         ...
         ...
</form>

I think this is cleaner than messing with the indexs in you views... Here is the post that explains how to do it step by step: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Nuget Package: http://www.nuget.org/packages/BeginCollectionItem/

Romias
  • 13,783
  • 7
  • 56
  • 85
0

I tried this and I couldn't get it to work with textboxfor. I used Textbox and specified the name.

<form method="post" action="/Home/Create">
@{var index = Guid.NewGuid();}
@Html.Textbox("products["+index +"].Name",Beer)
@Html.Textbox("products["+index +"].Price",7.32)
.
.
.
<input type="submit" />
</form>

You don't need a hidden index for MVC 3.0.

Let me know if this does not work I have a way of doing this, I'm just taking this from the top of my head.

Michał Powaga
  • 22,561
  • 8
  • 51
  • 62
David
  • 5,403
  • 15
  • 42
  • 72
  • I'm try to get this to work as you mentioned without having to use the hidden input for the index and I can't quite get it to work. Do you have a working example? – Nick Olsen Aug 22 '11 at 17:05