Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model binding error with custom property getter

I have getting an error during the model bind when I use try to bind a 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 change the properties to basic auto properties:

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

Everything works fine.

Why would the model binding have issues when the setters are the same as the auto property setters?

Edit - Reading through the default model binder source eventually leads you to this, where the first line is calling Clear() against the property, so when I returned the Empty<T> it obviously is not going to 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);
        }
    }
}
like image 609
asawyer Avatar asked Dec 13 '22 01:12

asawyer


2 Answers

Try like this:

get 
{
    return _drivers ?? new List<Claimant>();
}
like image 93
Darin Dimitrov Avatar answered Dec 26 '22 14:12

Darin Dimitrov


IIRC Enumerable.Empty<T> is a static, read-only enumerable used to pass an empty storage-agnostic enumerable to methods. It is not meant to be used as a "starting point" for an empty collection. That is likely why you're getting the error.

Choose a storage mechanism (e.g. List<T>) and use that as the type for your backing field. You can then either initialize it

  1. in the class definition,
  2. in the constructor, or
  3. at 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;
    }
}
like image 23
D Stanley Avatar answered Dec 26 '22 13:12

D Stanley