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.
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:
I changed the name of the property to something different from the class name and it started working:
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 aCompany
Type toCompanyAddress
type.To fix your problem, you simply have to rename the parameter
company
tocompanyAddressModel
- or anything which is not a property in your model class.change to:
See here for more information about model binding: http://aspnetmvc.readthedocs.org/projects/mvc/en/latest/models/model-binding.html
A good example from ASP.NET WebAPI documentation, which is using the same technique:
Here the
Id
property ofProduct
is mapped to theid
parameter in the controller. Which will work, cause in the action the same primitive data type is used as in the model class.Ensure your ViewModel is exposing properties and not just fields.
This works:
This doesn't:
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!