RC ModelBinder breaking changes for collections

Rate It (1)

Last post 11-18-2009 1:56 PM by levib. 16 replies.

Sort Posts:

  • RC ModelBinder breaking changes for collections

    01-29-2009, 5:54 PM
    • Member
      8 point Member
    • bchance
    • Member since 01-19-2009, 5:51 PM
    • Posts 19

    Asked this on Scott Guthries blog and he suggested I post the question here: 

    Have a question about a breaking change with the ModelBinder. In the beta, when filling a collection it would look for a .index form field to know what indexes to process. This worked great because I could output one .index per item (order.Products.index = 1 would look for order.Products[1].<fieldname>), then when I added a new one I could use negative numbers (so orders.Products[-1].<fieldname>). You could have as many index fields and the HTTP post would make a nice comma seperated list of values. Super easy to inject new html for a new item into the page.

    In the RC, it always starts at 0 and goes up until it cannot find any more. Besides making it harder to add because you have to know the next index, if you delete a member in the middle of your set, every one after will not get processed. For example, if 3 products were output ([0],[1],[2]), the middle one ([1])  is deleted via an AJAX call then the whole screen is saved, only the first ([0]) will be materialized with the model binder.

    With the beta you could also use anything for the index, they did not have to be integers. This was nice.

    Is there anyway to get the Beta functionality back, maybe flex if there is a .index? Or make some of the internal/private static methods more accessible (like DictionaryHelpers.DoesAnyKeyHavePrefix, VerifyValueUsability, CollectionHelpers.ReplaceCollection, ShouldUpdateProperty would also be useful). I have already written a new binder that handles the case, but would be nice out of the box (plus I cannot match exactly without some of the helper routines).

     Brian Chance

  • Re: RC ModelBinder breaking changes for collections

    01-30-2009, 1:49 PM
    • Contributor
      6,916 point Contributor
    • levib
    • Member since 07-23-2007, 7:50 PM
    • Redmond, WA
    • Posts 1,010
    • AspNetTeam

    The Beta behavior was fairly disliked by the community, so it's unlikely that it will be reintroduced.  Some possible solutions:

    If you're using AJAX to manipulate the set, then you shouldn't have to send the whole thing back to the server, since ostensibly each AJAX request would be responsible for adding, modifying, or deleting a single entry.

    If you're using Javascript (sans AJAX) for deletions, you could replace products[1] with a dummy value.  For example:

    products[0].ProductName = "Chai Tea"
    products[0].UnitPrice = "2.50"
    products[1] = ""
    productsToDelete = 1
    products[2].ProductName = "Colombian Coffee"
    products[2].UnitPrice = "3.00"

    Note the introduction of two values, a placeholder for Products[1] and a key telling us which products in the array should be deleted.  Pull this back like so:

    public ActionResult MyMethod(List<Product> products, int[] productsToDelete) {
      // iterate through productsToDelete (backward), deleting the specified indices from the products collection
    }

    Alternatively, have some Javascript that keeps track of the highest index and the index that was just deleted, and upon a product being deleted silently change the names of all the fields corresponding to the last product to match the product that was just deleted, then decrement the highest index count by one.  This will plug any gaps in the list on submission to the server but is a little bit more work on the client.

    Hope this helps! :)

  • Re: RC ModelBinder breaking changes for collections

    01-30-2009, 4:37 PM
    • Member
      6 point Member
    • dmiser
    • Member since 01-28-2009, 9:11 PM
    • Posts 7
    The other problem with the new binding implementation is that there is no way to tie back a field that has an error. In your example above, say products[2].UnitPrice gets set to -3. In the binder, I don't know which field to tie the AddModelError to, unless I add the primary key in, cluttering the HTML and requiring manual looping over the bindingContext to find the right PK, which seems awfully inefficient.
  • Re: RC ModelBinder breaking changes for collections

    01-30-2009, 5:00 PM
    • Contributor
      6,916 point Contributor
    • levib
    • Member since 07-23-2007, 7:50 PM
    • Redmond, WA
    • Posts 1,010
    • AspNetTeam

    The binder always has full knowledge of the entire field name that it's currently checking.  In your example, the binder knows that it's trying to match a property UnitPrice from the field prefixed products[2], so it concatenates them to form a full field name of products[2].UnitPrice.

    If that's not the scenario you're operating in, please clarify.  It's possible that I just misunderstood what you were trying to do. :)

  • Re: RC ModelBinder breaking changes for collections

    01-31-2009, 11:52 AM
    • Member
      8 point Member
    • bchance
    • Member since 01-19-2009, 5:51 PM
    • Posts 19

    I thnk what he is trying to say is that using a known index is easier to work with than a sequential number. products[<ProductID>].UnitPrice makes it easier to find related fields in JavaScript and server side code.

    I know the validation message helpers output using the modelstate, but if you wanted to make messages based on other fields, they are easier to find (i.e. UnitPrice must be greater than UnitCost). Having to rely on positional notation when you could have a known key is shaky. Suppose I wanted to load a list of products from the database, they push the values in by calling UpdateModel for each. With positional notation, I would need to loop through all product[x].ProductID to find the match, then call update model with the correct product[x] prefix. With key based, I know they are product[<ProductID>], much easier.

     On your previous comment to me: I understand where you guys believe the sequential is better, but honestly the code could flex based on the existence of a .index field and allow not integer indexes without too many changes. In the beta, I had honestly wished it would have a without a .index because sometimes sequential is just fine.

    Right now, everyone who used the beta code with model binding collections is broke.

    Here is an implementation that flexes based on index, not this only handles ICollection<> interfaces, I may get to IDictionary<,> and straight arrays when I get a chance. If so, I will post them here. (This should compile, I quickly took apart my LLBLGen model binder in notepad)

     

    //Set ModelBinders.Binders.DefaultBinder = new IndexModelBinder(); in Global.asax.cs Application_Start
    
    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Linq;
    using System.Text;
    using System.Web.Mvc;
    using System.ComponentModel;
    using System.Reflection;
    
    namespace System.Web.Mvc
    {
        public class IndexModelBinder : DefaultModelBinder
        {
            #region handle Beta style collections with .index
            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                //handle collections with an indexer specified
                Type collType = bindingContext.ModelType.GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ICollection<>));
                if (collType != null)
                {
                    //check for existence of model name fields, if not, fall back to empty model name
                    if (!string.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.Keys.Any(s => s.StartsWith(bindingContext.ModelName)))
                    {
                        //this should only be true for top level objects
                        if (!bindingContext.FallbackToEmptyPrefix) return null;
    
                        bindingContext = new ModelBindingContext() { Model = bindingContext.Model, ModelState = bindingContext.ModelState, ModelType = bindingContext.ModelType, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider };
                    }
    
                    ValueProviderResult vpr;
                    if (bindingContext.ValueProvider.TryGetValue(DefaultModelBinder.CreateSubPropertyName(bindingContext.ModelName, "index"), out vpr))
                    {
                        Type elementType = collType.GetGenericArguments()[0];
                        object model = bindingContext.Model ?? CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
                        if (typeof(ICollection<>).MakeGenericType(new Type[] { elementType }).IsInstanceOfType(model))
                        {
                            ModelBindingContext collContext = new ModelBindingContext() { Model = model, ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, ModelType = bindingContext.ModelType, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider };
                            return UpdateCollectionByIndex(controllerContext, collContext, elementType, (string[])vpr.RawValue);
                        }
                    }
                }
    
                return base.BindModel(controllerContext, bindingContext);
            }
    
            protected object UpdateCollectionByIndex(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType, string[] indexes)
            {
                IModelBinder binder = this.Binders.GetBinder(elementType);
                List<object> items = new List<object>();            
    
                foreach (string index in indexes)
                {
                    string prefix = string.Format("{0}[{1}]", bindingContext.ModelName, index);
                    if (!bindingContext.ValueProvider.Keys.Any(s => s.StartsWith(prefix))) continue;
    
                    ModelBindingContext objContext = new ModelBindingContext() { ModelName = prefix, ModelState = bindingContext.ModelState, ModelType = elementType, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider };
                    object obj = binder.BindModel(controllerContext, objContext);
                    if (obj != null) items.Add(obj);
                }
    
                if (items.Count == 0) return null;
    
                object collection = bindingContext.Model;
                Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
                MethodInfo addMethod = collectionType.GetMethod("Add");
                MethodInfo clearMethod = collectionType.GetMethod("Clear");
    
                clearMethod.Invoke(collection, null);
                items.ForEach(o => addMethod.Invoke(collection, new object[] { o }));
                return collection;
                
            }
            #endregion
    
        }
    }
    
    
     
  • Re: RC ModelBinder breaking changes for collections

    01-31-2009, 3:38 PM
    • Contributor
      6,916 point Contributor
    • levib
    • Member since 07-23-2007, 7:50 PM
    • Redmond, WA
    • Posts 1,010
    • AspNetTeam

    bchance:
    Right now, everyone who used the beta code with model binding collections is broke.

    That is incorrect.  The overwhelming majority of users disliked the .index notation, but they put up with it anyway and just used sequential zero-based lists.  Those applications still work (because they use zero-based lists), it's just that the .index fields are unnecessary now and are ignored by the binder.  Initial feedback from the community is that this change did much more good than bad.

    I see your point, though, but I don't have much hope for .index notation making a comeback for RTM.  Still, it couldn't hurt to ask around internally to see what the other devs think. :)  I'll try to have a more definitive answer for you next week.

  • Re: RC ModelBinder breaking changes for collections

    01-31-2009, 5:31 PM
    • Member
      6 point Member
    • dmiser
    • Member since 01-28-2009, 9:11 PM
    • Posts 7

    I'll try to put together a complete sample, but here was/is my use case. Assume Author and Book classes. In this case, an Author has a list of Books, and Book has a property to point back to the only Author (we'll ignore many to many for this discussion).

    On page 1, we build up the Author object (demographic info, royalty, etc.). Pressing submit, you go to the books page where you tuck the Author values into hidden fields (e.g. Author.Name) and allow for an existing list of Books to be edited. The binding on this page is still with Author, and if you create HTML fields like Author.Books[0].Price, everything binds up perfectly in RC1.

    The problem I'm having is tying Author.Books[0].Price back to the actual HTML element so I can display validation errors (assume it's more complex than simply checking negative values so we don't get hung up there).

    For now, I've changed the parameter on the controller from Author to IList<Book>, and by doing that, I can use the IndexModelBinder above (nice work, btw).I also have to new up an Author and populate it by bringing the hidden elements in manually.

    So to sum up, I'm trying to use multiple pages to build up one object that has lists of other objects. On the final page, I would submit the object to the repository for saving. Am I just completely missing the point here and making things too tough on myself?

  • Re: RC ModelBinder breaking changes for collections

    02-02-2009, 9:24 AM
    • Member
      8 point Member
    • bchance
    • Member since 01-19-2009, 5:51 PM
    • Posts 19

    @levib - it is always good to have options. The sequential is nice and easy, but does break down in more complex scenarios and having an easy, workable fallback instead of writing a manual code would save a lot of time.

    If it does not make it into the RTM, could we get some of the private static methods visibility changed to protected? Like VerifyValueUsability, ShouldUpdateProperty, ExtractGenericInterface, CanUpdateReadonlyTypeReference and ConvertProviderResult? All of these would make it easier to create overridden model binders that behave the same way.

    Also, CreateSubIndexName could take an object instead of an int, which would also help.

     

    @dmiser - it doesn't sound like you are making it too complex, just need the flexibility

     

  • Re: RC ModelBinder breaking changes for collections

    02-02-2009, 12:53 PM
    Answer
    • Contributor
      5,697 point Contributor
    • Eilon
    • Member since 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 966

    Hi everyone,

    We opened a work item to reconsider this behavior. As Levi mentioned, it's not likely to make it in for ASP.NET MVC v1.0 since it's so late in the game, but it's on our list.

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
  • Re: RC ModelBinder breaking changes for collections

    02-03-2009, 1:50 PM
    • Member
      16 point Member
    • danambrose
    • Member since 01-16-2003, 1:57 AM
    • Posts 8

    I am another one who is unhappy that this behavior changed in RC1. I have found another change that has broken things for me. Say I have a controller action that gets posted to:

     [AcceptVerbs("POST")]

    public string SaveBOM(int id,decimal? freight,decimal? packaging,decimal? other2,decimal? totalpieceprice,string items)

    {

    //Definition...

    }

    I am passing xml through the items parameter. In beta, this works great. But now, this action is never being called unless I comment out the "string items" parameter. In my code its being called synchronously via AJAX. I set a break point that never gets hits now.

    There is definately a change in how the items parameter is being handled.

     If anyone can help, it would be greatly appreciated.

  • Re: RC ModelBinder breaking changes for collections

    02-03-2009, 2:02 PM
    • Contributor
      6,916 point Contributor
    • levib
    • Member since 07-23-2007, 7:50 PM
    • Redmond, WA
    • Posts 1,010
    • AspNetTeam

    Dan - Please open a new thread for your question so that we can handle it separately.  Thanks.

  • Re: RC ModelBinder breaking changes for collections

    02-03-2009, 2:31 PM
    • Member
      8 point Member
    • bchance
    • Member since 01-19-2009, 5:51 PM
    • Posts 19

    Here is an implemenatation that handles collections/lists, arrays and dictionaries that have a .index, otherwise it falls through to the RC code. NOTE: this will not fall back to a blank prefix for method parameter binding. Use the BindAttribute to specify a blank. I did not want to modify the original context if not necessary.

     

    /* Usage documentation at the bottom of this file */
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections;
    using System.Reflection;
    
    namespace System.Web.Mvc
    {
    	public class IndexModelBinder : DefaultModelBinder
    	{
    		#region handle Beta style collections with .index
    		public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    		{
    			//handle collections with an .index specified
    			Type modelType = bindingContext.ModelType;
    			object model = bindingContext.Model;
    
    			Type enumerableType = GetInterface(modelType, typeof(IEnumerable<>));
    			Type dictionaryType = GetInterface(modelType, typeof(IDictionary<,>));
    
    			if ((model == null && modelType.IsArray) ||
    				dictionaryType != null ||
    				enumerableType != null)
    			{
    				ValueProviderResult vpr;
    				if (bindingContext.ValueProvider.TryGetValue(DefaultModelBinder.CreateSubPropertyName(bindingContext.ModelName, "index"), out vpr))
    				{
    					string[] indexes = (string[])vpr.RawValue;
    
    					if (model == null && modelType.IsArray)
    					{
    						Type elementType = modelType.GetElementType();
    						Type listType = typeof(IList<>).MakeGenericType(elementType);
    						object collection = CreateModel(controllerContext, bindingContext, listType);
    
    						ModelBindingContext arrayContext = new ModelBindingContext()
    						{
    							Model = collection,
    							ModelName = bindingContext.ModelName,
    							ModelState = bindingContext.ModelState,
    							ModelType = listType,
    							PropertyFilter = bindingContext.PropertyFilter,
    							ValueProvider = bindingContext.ValueProvider
    						};
    						IList list = (IList)UpdateCollectionByIndex(controllerContext, arrayContext, elementType, indexes);
    
    						if (list == null) return null;
    
    						Array array = Array.CreateInstance(elementType, list.Count);
    						list.CopyTo(array, 0);
    						return array;
    					}
    
    					if (model == null) model = CreateModel(controllerContext, bindingContext, modelType);
    
    					if (dictionaryType != null)
    					{
    						Type[] types = dictionaryType.GetGenericArguments();
    						ModelBindingContext dictionaryContext = new ModelBindingContext()
    						{
    							Model = model,
    							ModelName = bindingContext.ModelName,
    							ModelState = bindingContext.ModelState,
    							ModelType = modelType,
    							PropertyFilter = bindingContext.PropertyFilter,
    							ValueProvider = bindingContext.ValueProvider
    						};
    						return UpdateDictionaryByIndex(controllerContext, dictionaryContext, types[0], types[1], indexes);
    					}
    
    					if (enumerableType != null)
    					{
    						Type elementType = enumerableType.GetGenericArguments()[0];
    						if (typeof(ICollection<>).MakeGenericType(new Type[] { elementType }).IsInstanceOfType(model))
    						{
    							ModelBindingContext collectionContext = new ModelBindingContext()
    							{
    								Model = model,
    								ModelName = bindingContext.ModelName,
    								ModelState = bindingContext.ModelState,
    								ModelType = modelType,
    								PropertyFilter = bindingContext.PropertyFilter,
    								ValueProvider = bindingContext.ValueProvider
    							};
    							return UpdateCollectionByIndex(controllerContext, collectionContext, elementType, indexes);
    						}
    					}
    				}
    			}
    
    			return base.BindModel(controllerContext, bindingContext);
    		}
    
    		private object UpdateDictionaryByIndex(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType, string[] indexes)
    		{
    			IModelBinder keybinder = Binders.GetBinder(keyType);
    			IModelBinder valuebinder = Binders.GetBinder(valueType);
    
    			List<object, object>> items = new List<object, object>>();
    			foreach (string index in indexes)
    			{
    				string prefix = string.Format("{0}[{1}]", bindingContext.ModelName, index);
    				string keyname = CreateSubPropertyName(prefix, "key");
    				string valuename = CreateSubPropertyName(prefix, "value");
    
    				if (!bindingContext.ValueProvider.Keys.Any(s => s.StartsWith(keyname))) continue;
    
    				ModelBindingContext keyContext = new ModelBindingContext()
    				{
    					ModelName = keyname,
    					ModelState = bindingContext.ModelState,
    					ModelType = keyType,
    					ValueProvider = bindingContext.ValueProvider
    				};
    
    				object key = keybinder.BindModel(controllerContext, keyContext);
    				if (key == null) continue;
    
    				ModelBindingContext valueContext = new ModelBindingContext()
    				{
    					ModelName = valuename,
    					ModelState = bindingContext.ModelState,
    					ModelType = valueType,
    					PropertyFilter = bindingContext.PropertyFilter,
    					ValueProvider = bindingContext.ValueProvider
    				};
    
    				object value = valuebinder.BindModel(controllerContext, valueContext);
    				Verify(bindingContext.ModelState, valuename, valueType, value);
    				items.Add(new KeyValuePair<object, object>(key, value));
    			}
    
    			if (items.Count == 0) return null;
    
    			object dictionary = bindingContext.Model;
    			ClearAndFillDictionary(keyType, valueType, dictionary, items);
    			return dictionary;
    		}
    
    		protected object UpdateCollectionByIndex(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType, string[] indexes)
    		{
    			IModelBinder binder = Binders.GetBinder(elementType);
    			List<object> items = new List<object>();
    
    			foreach (string index in indexes)
    			{
    				string prefix = string.Format("{0}[{1}]", bindingContext.ModelName, index);
    				if (!bindingContext.ValueProvider.Keys.Any(s => s.StartsWith(prefix))) continue;
    
    				ModelBindingContext objContext = new ModelBindingContext()
    				{
    					ModelName = prefix,
    					ModelState = bindingContext.ModelState,
    					ModelType = elementType,
    					PropertyFilter = bindingContext.PropertyFilter,
    					ValueProvider = bindingContext.ValueProvider
    				};
    				object obj = binder.BindModel(controllerContext, objContext);
    				Verify(bindingContext.ModelState, prefix, elementType, obj);
    				items.Add(obj);
    			}
    
    			if (items.Count == 0) return null;
    
    			object collection = bindingContext.Model;
    			ClearAndFillCollection(elementType, collection, items);
    			return collection;
    		}
    
    				
    		protected static readonly MethodInfo _clearAndFillCollectionMethod = typeof(IndexModelBinder).GetMethod("ClearAndFillCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic);
    		protected static void ClearAndFillCollection(Type collectionType, object collection, IEnumerable contents)
    		{
    			_clearAndFillCollectionMethod.MakeGenericMethod(collectionType).Invoke(null, new object[] { collection, contents });
    		}
    
    		protected static void ClearAndFillCollectionImpl(ICollection collection, IEnumerable contents)
    		{
    			collection.Clear();
    			foreach (object o in contents)
    				collection.Add((o is T) ? (T)o : default(T));
    		}
    
    		protected static readonly MethodInfo _clearAndFillDictionaryMethod = typeof(IndexModelBinder).GetMethod("ClearAndFillDictionaryImpl", BindingFlags.Static | BindingFlags.NonPublic);
    		protected static void ClearAndFillDictionary(Type keyType, Type valueType, object dictionary, IEnumerable<object, object>> contents)
    		{
    			_clearAndFillDictionaryMethod.MakeGenericMethod(keyType, valueType).Invoke(null, new object[] { dictionary, contents });
    		}
    
    		protected static void ClearAndFillDictionaryImpl(IDictionary dictionary, IEnumerable<object, object>> contents)
    		{
    			dictionary.Clear();
    			foreach (var o in contents)
    				dictionary[(TKey)o.Key] = (o.Value is TValue) ? (TValue)o.Value : default(TValue);
    		}
    
    		protected static readonly MethodInfo _verifyMethod = typeof(DefaultModelBinder).GetMethod("VerifyValueUsability", BindingFlags.Static | BindingFlags.NonPublic);
    		protected static void Verify(ModelStateDictionary modelState, string modelStateKey, Type elementType, object value)
    		{
    			_verifyMethod.Invoke(null, new object[] { modelState, modelStateKey, elementType, value });
    		}
    
    		protected static readonly MethodInfo _extractInterface = typeof(DefaultModelBinder).GetMethod("ExtractGenericInterface", BindingFlags.Static | BindingFlags.NonPublic);
    		protected static Type GetInterface(Type queryType, Type interfaceType)
    		{
    			return _extractInterface.Invoke(null, new object[] { queryType, interfaceType }) as Type;
    		}
    		#endregion
    	}
    }
    
    /*
    To Use, set the default binder in the Global.asax.cs Application_Start()
    ModelBinders.Binders.DefaultBinder = new IndexModelBinder();
    
    I have implemented the beta behaviour if an index field exists, otherwise it passes through to the RC1 method of sequential.
    NOTE: This will not automatically fall back to a blank model name for top level parameters, use a BindAttribute to specify a blank name
    
    RC1 behaviour: Name your fields company.Contacts[0].FieldName, counting up from 0
        
        <% for (int i=0;i<Model.Contacts.Count;i++) 
            ContactEntity contact = Model.Contacts[i]; {%>
        <tr>
            <td>Contacts.ContactId</td>
            <td><%= Html.TextBox("company.Contacts[" + i + "].ContactId", contact.ContactId)%></td>
        </tr>
        <tr>
            <td>Contacts.FirstName</td>
            <td><%= Html.TextBox("company.Contacts[" + i + "].FirstName", contact.FirstName)%></td>
        </tr>
        <tr>
            <td>Contacts.LastName</td>
            <td><%= Html.TextBox("company.Contacts[" + i + "].LastName", contact.LastName)%></td>
        </tr>
        <% } %>
     
    To add, you need to add a new set of fields numbered one higher.
     
    Beta behaviour: looks for a field (.index) that holds the index values to iterate:
        
        <% foreach (ContactEntity contact in Model.Contacts) {%>
        <tr>
            <td>Contacts.index</td>
            <td><%= Html.TextBox("company.Contacts.index", contact.ContactId)%></td>
        </tr>
        <tr>
            <td>Contacts.ContactId</td>
            <td><%= Html.TextBox("company.Contacts[" + contact.ContactId + "].ContactId", contact.ContactId)%></td>
        </tr>
        <tr>
            <td>Contacts.FirstName</td>
            <td><%= Html.TextBox("company.Contacts[" + contact.ContactId + "].FirstName", contact.FirstName)%></td>
        </tr>
        <tr>
            <td>Contacts.LastName</td>
            <td><%= Html.TextBox("company.Contacts[" + contact.ContactId + "].LastName", contact.LastName)%></td>
        </tr>
        <% } %>
    
    if you want to add a new object, create any unique index:
    <%= Html.TextBox("company.Contacts.index", -1)%>
    <%= Html.TextBox("company.Contacts[-1].ContactId", 0)%>
    <%= Html.TextBox("company.Contacts[-1].FirstName")%>
    */
     
  • Re: RC ModelBinder breaking changes for collections

    02-03-2009, 2:42 PM
    • Member
      16 point Member
    • danambrose
    • Member since 01-16-2003, 1:57 AM
    • Posts 8

    Thank you! This really helps me out.

  • Re: RC ModelBinder breaking changes for collections

    08-23-2009, 3:29 PM
    • Member
      2 point Member
    • clazarr
    • Member since 08-23-2009, 7:21 PM
    • Posts 1

    Since we are now seeing previews for ASP.NET MVC v2, is this "flex" capability being considered to make it into a preview? How about the other suggestion bchance made about changing visibility to protected for certain methods to facilitate easier and more consistently implemented custom binders?

    Keep up the great work with MVC and the openness to suggestions!

    Thanks,

    Chuck

    Filed under:
  • Re: RC ModelBinder breaking changes for collections

    08-24-2009, 12:38 PM
    • Contributor
      5,697 point Contributor
    • Eilon
    • Member since 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 966

    Hi Chuck,

    We still have an active work item on our list for ASP.NET MVC 2 to investigate this issue. At the moment we have other items that we feel are higher priority so we haven't had a chance to investigate adding back the index-based behavior.

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
Page 1 of 2 (17 items) 1 2 Next >