Model binding error with custom getter property

I get an error while binding the model when I try to bind the model with these two properties:

private IEnumerable<Claimant> _drivers; public IEnumerable<Claimant> Drivers { get { return _drivers ?? Enumerable.Empty<Claimant>(); } set { _drivers = value; } } private IEnumerable<Property> _vehicles; public IEnumerable<Property> Vehicles { get { return _vehicles ?? Enumerable.Empty<Property>(); } set { _vehicles = value; } } 

Error:

 System.Reflection.TargetInvocationException was unhandled by user code Message=Exception has been thrown by the target of an invocation. Source=mscorlib StackTrace: <snip> InnerException: System.NotSupportedException Message=Collection is read-only. Source=mscorlib StackTrace: at System.SZArrayHelper.Clear[T]() at System.Web.Mvc.DefaultModelBinder.CollectionHelpers .ReplaceCollectionImpl[T](ICollection`1 collection, IEnumerable newContents) InnerException: 

If I changed the properties to basic automatic properties:

  public IEnumerable<Claimant> Drivers { get; set; } public IEnumerable<Property> Vehicles { get; set; } 

Everything is working fine.

Why does model binding have problems when setters are the same as auto networks?

Change Reading through the default binding source by default will eventually lead you to this, where the first line calls Clear() against the property, so when I returned Empty<T> , it obviously won't work.

 private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents) { collection.Clear(); if (newContents != null) { foreach (object item in newContents) { // if the item was not a T, some conversion failed. the error message will be propagated, // but in the meanwhile we need to make a placeholder element in the array. T castItem = (item is T) ? (T)item : default(T); collection.Add(castItem); } } } 
+4
source share
4 answers

Try it like this:

 get { return _drivers ?? new List<Claimant>(); } 
+6
source

IIRC Enumerable.Empty<T> is a static, read-only enumerable used to pass to empty aggregated aggregate storage methods. It is not intended to be used as a โ€œstarting pointโ€ for an empty collection. This is probably why you get the error.

Select a storage engine (e.g. List<T> ) and use it as a type for your support field. You can either initialize it

  • in class definition
  • in the constructor or
  • first get:

examples:

 private List<Claimant> _drivers = new List<Claimamt>(); // Option 1 public MyModel() { _drivers = new List<Claimant>(); // Option 2 } public IEnumerable<Claimant> Drivers { get { return _drivers ?? (_drivers = new List<Claimant>()); // Option 3 } set { _drivers = value; } } 
+5
source

In custom getter, I usually saw the null coalescing operator (??) used to set the private support field when returning:

 private IEnumerable<Claimant> _drivers; public IEnumerable<Claimant> Drivers { get { return _drivers ?? (_drivers = Enumerable.Empty<Claimant>()); } set { _drivers = value; } } 

You can also try to set the default value for an empty array:

 (_drivers = new Claimant[]{}) 
+1
source

Find out the exact name and type of property that is causing the problem:

 public class TestBinder: DefaultModelBinder { public override object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext) { Debug.WriteLine(bindingContext.ModelName); Debug.WriteLine(bindingContext.ModelType.ToString()); //HOW TO USE: Look at your Output for the most recently output ModelName and Type to determine where there problem lies return base.BindModel(controllerContext, bindingContext); } } 

Set this binder as the default:

 ModelBinders.Binders.DefaultBinder = new TestBinder(); 
+1
source

All Articles