4

I am trying to do a post operation in asp.net mvc 6 and expecting the complex property collection to be initialized properly. But it is always empty.

I am creating input html element with proper index:

This is an HTML FORM for POST:

@model MainObject
<form asp-action="create" method="post">
  <input asp-for="ChildObjects[0].RollNumber" />
  <input type="submit" value="create" />
</form>

Controller Code

public async Task<IActionResult> Create(MainObject mainObj)
{
    // The mainObj.ChildObjects remains empty.
}

My view will contain only one child object entry, that's why only 0 index used.

The form data contains the above key and value but when it reaches the controller action the collection property is empty i.e. MainObject.ChildObjects has count 0. (Note: The ChildObjects list is already initialized in my MainObject constructor)

Models:

public class MainObject {
   public MainObject() {
      this.ChildObjects = new List<ChildObjects>();
   }

   public IList<ChildObject> ChildObjects {get; private set;}
}

On looking up the ModelState property in constructor in debug mode, it shows one Error for ChildObjects key, but the error message is too generic:

Object reference not set to instance of an object.

I have followed many articles on net for model binding complex collection, but somehow it is not working for me.

8
  • Your usage of asp-for is suspect, you probably want value=Model.ChildObjects[0].RollNumber. But if you've got an empty reference, you need to post your controller code where you are instantiating this MainObject and forwarding it to the view also. Commented Apr 4, 2016 at 23:21
  • asp-for creates a name tag, which is expected. Value attribute is used for the actual value. Added the controller code Commented Apr 4, 2016 at 23:24
  • @StaffordWilliams asp-for is MVC6 (or MVC core if you like) syntax for tag helpers. Commented Apr 4, 2016 at 23:34
  • @DavigG aware, but thought that tag was supposed to be in a <label>, however I see here that it does also go in the <input>. Commented Apr 4, 2016 at 23:36
  • @Jash your controller action is not passing the object back to the view; ie: return View(mainObj) Commented Apr 4, 2016 at 23:37

1 Answer 1

8

Declaring the child object collection with private set blocks the binder from setting the collection values. The setter must be public so MVC6 can set the values in the postback;

public IList<ChildObject> ChildObjects {get; private set;} // empty on postback
public IList<ChildObject> ChildObjects {get; set;} // populated on postback

The collection is still instantiated however (rather than null, and hence count == 0) when the model binder calls the parameterless constructor you have declared.

Sign up to request clarification or add additional context in comments.

6 Comments

Wow, so this was the issue. After removing the private setter it worked. Thanks Stafford. Aside, what if i want to have the private setter, then are there any alternatives?
@Jash You probably want it to be private due to some kind of business logic. Don't stick business logic in your models, they should just be shells that transport the data from your controller to your view for binding. Stick the business logic in whatever holds that data prior to you sticking it in the view.
I thought, we should always have the collections property as read only, and it has nothing to do with business logic but more of a best coding practise. msdn.microsoft.com/en-us/library/ms182327(VS.80).aspx
Business logic was probably the wrong term, best practice is likely a better one. However all best practices and/or patterns are at best a guide and there are cases where they do not apply. With MVC<6 (and possibly with MVC6, i'm not certain) you could write a custom model binder to hack your way around this. However adding such complexity to what should (as mentioned earlier) be just a boring shell for transferring/binding data is going beyond the intentions of the best practice you've linked. If models were expected to do more than just bind data, then sure, your link is valid.
@StaffordWilliams: Shouldn't it also work with a preinstantiated list in the model? i.e. using property initializers: public IList<ChildObject> ChildObjects {get; } = new List<ChildObject>(); ?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.