7

I'm passing a ViewModel back from my View to the Controller via a form HttpPost. However, the values returned are always NULL.

ViewModel

public class vmCompanyAddress
{
    public StatelyTechAdmin.Models.Company Company { get; set; }
    public StatelyTechAdmin.Models.CompanyAddress Address { get; set; }

    public SelectList Counties { get; set; }
}

Company Class Model

public class Company
{
    [Key]
    public virtual long CompanyId { get; set; }

    [Required]
    [Display(Name = "Company Name")]
    public virtual string Name { get; set; }

    public virtual DateTime CreatedDate { get; set; }

    public virtual IEnumerable<CompanyAddress> CompanyAddresses { get; set; }
}

CompanyAddress Class Model

public class CompanyAddress
{
    [Key]
    public virtual long CompanyAddressId { get; set; }

    [Required]
    public virtual long CompanyId { get; set; }

    [ForeignKey("CompanyId")]
    public virtual Company Company { get; set; }

    [Required]
    public virtual int CopmanyAddressTypeId { get; set; }

    [ForeignKey("CopmanyAddressTypeId")]
    public virtual CompanyAddressType CompanyAddressType { get; set; }

    [Display(Name = "Address 1")]
    public virtual string Address1 { get; set; }

    [Display(Name = "Address 2")]
    public virtual string Address2 {get; set; }

    [Display(Name = "Town")]
    public virtual string Town { get; set; }

    [Display(Name = "City")]
    public virtual string City { get; set; }

    [Required]
    public virtual long CountyId { get; set; }

    [ForeignKey("CountyId")]
    [Display(Name = "County")]
    public virtual County County { get; set; }

    [Required]
    [Display(Name = "Postal Code")]
    public virtual string PostalCode { get; set; }

    public virtual DateTime CreatedDate { get; set; }
}

Controller (get):

// GET: /Company/Create
    public ActionResult Create()
    {
        vmCompanyAddress vm = new vmCompanyAddress();
        vm.Counties = new SelectList(db.County, "CountyId", "Name", -1);
        //vm.Address = new CompanyAddress();
        //vm.Company = new Company();

        return View(vm);
    }

Controller (post):

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(vmCompanyAddress company)
    {
        if (ModelState.IsValid)
        {
            db.Companies.Add(company.Company);

            //Amend Address Company & Address Type before save to DB
            company.Address.CompanyId = company.Company.CompanyId;
            company.Address.CopmanyAddressTypeId = 1;

            db.CompanyAddress.Add(company.Address);

            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(company);
    }

View (create)

    @model StatelyTechAdmin.ViewModels.vmCompanyAddress

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Company</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Company.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Company.Name)
            @Html.ValidationMessageFor(model => model.Company.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Company.CreatedDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Company.CreatedDate)
            @Html.ValidationMessageFor(model => model.Company.CreatedDate)
        </div>


        @* Invoice Address *@
        <div class="editor-label">
            @Html.LabelFor(model => model.Address.Address1)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address.Address1)
            @Html.ValidationMessageFor(model => model.Address.Address1)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Address.Address2)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address.Address2)
            @Html.ValidationMessageFor(model => model.Address.Address2)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Address.Town)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address.Town)
            @Html.ValidationMessageFor(model => model.Address.Town)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Address.City)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address.City)
            @Html.ValidationMessageFor(model => model.Address.City)
        </div>

        @*<div class="editor-label">
            @Html.LabelFor(model => model.Address.County)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.Address.CountyId, Model.Counties)
        </div>*@

        <div class="editor-label">
            @Html.LabelFor(model => model.Address.PostalCode)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address.PostalCode)
            @Html.ValidationMessageFor(model => model.Address.PostalCode)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

Can anyone please offer any advice as to why my return ViewModel values are NULL when all fields are populated?

I've checked in Google Chrome browser using the Network Record feature and all values ARE posted back in JSON format.

Many thanks.

------------ EDIT ---------------

Here's part of what I can see from the Google Chrome Network Monitor

Company.Name:ABC123 Company.CreatedDate:2014/05/13 00:00:00 ....

So it is definitely being returned.

tereško
  • 58,060
  • 25
  • 98
  • 150
RobHurd
  • 2,041
  • 7
  • 26
  • 34
  • Funny enough, someone has had a similar issue with posting back "Company.Name". For funsies, try removing that field and see if it still posts back everything null. http://stackoverflow.com/questions/780026/asp-net-mvc-model-binding-returning-null-values – Erik Elkins May 13 '14 at 20:05
  • 1
    CompanyAddressTypeId is spelled wrong. Also, try defining your form like this - Html.BeginForm("yourControllerNameHere", "Create", FormMethod.Post) – JB06 May 13 '14 at 20:18
  • Thanks @ErikElkins however, removing Company.Name from my View didn't make a difference. Everything still posts back as NULL. – RobHurd May 13 '14 at 20:33
  • @Josh I tried Amending the BeginForm(), but it didn't work. Thanks for the input and pointing out the spelling error. – RobHurd May 13 '14 at 21:07
  • Is the action being hit in the debugger? Are you registering any special binding filters? – mxmissile May 13 '14 at 22:13
  • @mxmissile yea the action is being hit in the debug mode, however vmCompanyAddress.Company=NULL and vmCompanyAddress.Address=NULL. VS2013 automatically created the Controller and Views based on the **Company** model, and not my ViewModel **vmCompanyAddress**. Could it be that somewhere it is expecting a **Company** model and not a **vmCompanyAddress** model? I modified my Create HTTPPost method to accept **vmCompanyAddress** object rather than the **Company** model it generated. – RobHurd May 13 '14 at 22:22
  • Do I need some kind of Constructor in my vmCompanyAddress class? – RobHurd May 13 '14 at 22:30
  • Worth checking if you have a redundant or invalid jQuery Ajax on the submit button click, which was the reason in my case, for the View Model being null on Post. – Jaggan_j May 09 '19 at 10:01

4 Answers4

15

I was able to reproduce your issue and was confused because I know that the default MVC Model Binder understands complex types. I stripped away most of the code and just tried to do it with the Company object, which still failed. I then noticed that in vmCompanyAddress that the name of the class was also the name of the property:

public class vmCompanyAddress
{
     public StatelyTechAdmin.Models.Company Company { get; set; }

I changed the name of the property to something different from the class name and it started working:

public class vmCompanyAddress
{
     public StatelyTechAdmin.Models.Company TheCompany { get; set; }
  • 4
    Absolutely spot on! Many thanks, really appreciate you looking into this for me. I certainly won't make the same mistake again. – RobHurd May 13 '14 at 23:01
  • 3
    You are very welcome. Although the compiler allows it, I am not a big fan of naming my properties the same as the Class name they are defined as. I view the class names as reserved words and therefore should be no confusion when referencing the class vs an instance of the class. – Matthew at Critical Cognition May 14 '14 at 16:26
10

We had the same problem today. The accepted answer in this question is only a dirty workaround for the actual problem.

ClassName and PropertyName in a form model can be the same, there is no limitation in the model binder. The limitation is the parameter of the action in your controller. You must not name the parameter like a property with complex type in your form model. Cause the binder will try to bind the HTTP POST form value of company to this paramter in your controller. It will not work for you, cause the binder tries to bind the values of a Company Type to CompanyAddress type.

To fix your problem, you simply have to rename the parameter company to companyAddressModel - or anything which is not a property in your model class.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompanyAddress company)

change to:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompanyAddress companyAddressModel)

See here for more information about model binding: http://aspnetmvc.readthedocs.org/projects/mvc/en/latest/models/model-binding.html

MVC will try to bind request data to the action parameters by name. MVC will look for values for each parameter using the parameter name and the names of its public settable properties. [...] In addition to route values MVC will bind data from various parts of the request and it does so in a set order. Below is a list of the data sources in the order that model binding looks through them:

  • Form values: These are form values that go in the HTTP request using the POST method.
  • Route values: The set of route values provided by routing.
  • Query strings: The query string part of the URI.

A good example from ASP.NET WebAPI documentation, which is using the same technique:

HttpResponseMessage Put(int id, Product item) { ... }

Here the Id property of Product is mapped to the id parameter in the controller. Which will work, cause in the action the same primitive data type is used as in the model class.

Martin Lantzsch
  • 1,851
  • 15
  • 19
2

Have not tried this myself but had a lot of similar issues a long time ago that I solved with custom ModelBinder:s which I do not recommend.

I guess your data does not look like: { Company: {...}, Address: {...} }?

I think the solution is to have MVC to understand the structure of the data using templates and EditorFor(). See http://lostechies.com/jimmybogard/2011/09/07/building-forms-for-deep-view-model-graphs-in-asp-net-mvc/ for a good example!

Fredrik C
  • 570
  • 6
  • 22
  • Thanks for the input. @MatthewAtCriticalCogni hit the nail on the head. My ViewModel property was named the same as my Class, thus causing it to fail. – RobHurd May 13 '14 at 23:03
2

Ensure your ViewModel is exposing properties and not just fields.

This works:

public DAL.Models.Account acct {get;set;}

This doesn't:

public DAL.Models.Account acct;
Lodlaiden
  • 361
  • 1
  • 10