DefaultModelBinder not binding nested model
Asked Answered
K

5

14

Looks like others have had this problem but I can't seem to find a solution.

I have 2 Models: Person & BillingInfo:

public class Person
{
 public string Name { get; set;}
 public BillingInfo BillingInfo { get; set; }
}

public class BillingInfo
{
 public string BillingName { get; set; }
}

And I'm trying to bind this straight into my Action using the DefaultModelBinder.

public ActionResult DoStuff(Person model)
{
 // do stuff
}

However, while the Person.Name property is set, the BillingInfo is always null.

My post looks like this:

"Name=statichippo&BillingInfo.BillingName=statichippo"

Why is BillingInfo always null?

Kuhlmann answered 13/10, 2010 at 21:20 Comment(2)
I had same issue, In my case I was missing the Set{} on the object in the ViewModel and also I had Hidden Field & TextBoxFor field defined for the same property, both values were getting posted, one was blank (hidden field) and other had the correct user input value (TextboxFor), but model binding always took the hidden field value over the user input value so it was always null.Duckling
@Duckling Thank you! I was missing the property setter as well, and could not find out why the nested object wouldn't bind. It's too bad so many refactoring tools recommend removing setters on ViewModels where the nested object is a collection, etc.Chandelle
E
6

Status no repro. Your problem is elsewhere and unable to determine where from what you've given as information. The default model binder works perfectly fine with nested classes. I've used it an infinity of times and it has always worked.

Model:

public class Person
{
    public string Name { get; set; }
    public BillingInfo BillingInfo { get; set; }
}

public class BillingInfo
{
    public string BillingName { get; set; }
}

Controller:

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new Person
        {
            Name = "statichippo",
            BillingInfo = new BillingInfo
            {
                BillingName = "statichippo"
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Person model)
    {
        return View(model);
    }
}

View:

<% using (Html.BeginForm()) { %>
    Name: <%: Html.EditorFor(x => x.Name) %>
    <br/>
    BillingName: <%: Html.EditorFor(x => x.BillingInfo.BillingName) %>
    <input type="submit" value="OK" />
<% } %>

Posted values: Name=statichippo&BillingInfo.BillingName=statichippo is perfectly bound in the POST action. Same works with GET as well.


One possible case when this might not work is the following:

public ActionResult Index(Person billingInfo)
{
    return View();
}

Notice how the action parameter is called billingInfo, same name as the BillingInfo property. Make sure this is not your case.

Evadnee answered 13/10, 2010 at 21:27 Comment(3)
You're right. Turns out my HTML had an issue and was outputting:Kuhlmann
premature enter ;) -- "Name=statichippo&BillingInfo=&BillingInfo.BillingName=statichippo"Kuhlmann
I had the same problem with a nested type not being binded. Turns out I also had problems with my HTML. I had 2 radio buttons where the name was the same as the property name on my view model. Radio button values are also posted, so the default model binder got confused.Canopus
M
14

I had this problem, and the answer was staring me in the face for a few hours. I'm including it here because I was searching for nested models not binding and came to this answer.

Make sure that your nested model's properties, like any of your models that you want the binding to work for, have the correct accessors.

    // Will not bind!
    public string Address1;
    public string Address2;
    public string Address3;
    public string Address4;
    public string Address5;


    // Will bind
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string Address3 { get; set; }
    public string Address4 { get; set; }
    public string Address5 { get; set; }
Mcgrath answered 29/5, 2015 at 14:22 Comment(1)
I rechecked and my viewmodels were also lacking { get; set; } - there is some kind of blindness that stops us seeing this!Morrill
E
6

Status no repro. Your problem is elsewhere and unable to determine where from what you've given as information. The default model binder works perfectly fine with nested classes. I've used it an infinity of times and it has always worked.

Model:

public class Person
{
    public string Name { get; set; }
    public BillingInfo BillingInfo { get; set; }
}

public class BillingInfo
{
    public string BillingName { get; set; }
}

Controller:

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new Person
        {
            Name = "statichippo",
            BillingInfo = new BillingInfo
            {
                BillingName = "statichippo"
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Person model)
    {
        return View(model);
    }
}

View:

<% using (Html.BeginForm()) { %>
    Name: <%: Html.EditorFor(x => x.Name) %>
    <br/>
    BillingName: <%: Html.EditorFor(x => x.BillingInfo.BillingName) %>
    <input type="submit" value="OK" />
<% } %>

Posted values: Name=statichippo&BillingInfo.BillingName=statichippo is perfectly bound in the POST action. Same works with GET as well.


One possible case when this might not work is the following:

public ActionResult Index(Person billingInfo)
{
    return View();
}

Notice how the action parameter is called billingInfo, same name as the BillingInfo property. Make sure this is not your case.

Evadnee answered 13/10, 2010 at 21:27 Comment(3)
You're right. Turns out my HTML had an issue and was outputting:Kuhlmann
premature enter ;) -- "Name=statichippo&BillingInfo=&BillingInfo.BillingName=statichippo"Kuhlmann
I had the same problem with a nested type not being binded. Turns out I also had problems with my HTML. I had 2 radio buttons where the name was the same as the property name on my view model. Radio button values are also posted, so the default model binder got confused.Canopus
S
1

I had the same issue, the previous developer on the project had the property registered with a private setter as he wasn't using this viewmodel in a postback. Something like this:

public MyViewModel NestedModel { get; private set; }

changed to this:

public MyViewModel NestedModel { get; set; }
Serigraph answered 25/1, 2017 at 9:24 Comment(0)
L
1

This is what worked for me.

I changed this:

[HttpPost]
    public ActionResult Index(Person model)
    {
        return View(model);
    }

To:

[HttpPost]
    public ActionResult Index(FormCollection fc)
    {
        Person model = new Person();
        model.BillingInfo.BillingName = fc["BillingInfo.BillingName"]

        /// Add more lines to complete all properties of model as necessary.

        return View(model);
    }
Lavallee answered 28/11, 2017 at 23:7 Comment(1)
Worked for me too. Thanks for that solution.Shwa
H
0
public class MyNestedClass
{
    public string Email { get; set; }
}

public class LoginModel
{
//If you name the property as 'xmodel'(other than 'model' then it is working ok.
public MyNestedClass xmodel  {get; set;} 

//If you name the property as 'model', then is not working
public MyNestedClass model  {get; set;} 

public string Test { get; set; }
}

I have had the similiar problem. I spent many hours and find the problem accidentally that I should not use 'model' for the property name

@Html.TextBoxFor(m => m.xmodel.Email) //This is OK
@Html.TextBoxFor(m => m.model.Email) //This is not OK
Helfant answered 3/5, 2013 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.