MVC View ViewModel HttpPost return value is always

2019-04-29 01:46发布

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.

4条回答
我想做一个坏孩纸
2楼-- · 2019-04-29 02:36

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; }
查看更多
干净又极端
3楼-- · 2019-04-29 02:40

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.

查看更多
时光不老,我们不散
4楼-- · 2019-04-29 02:42

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;
查看更多
对你真心纯属浪费
5楼-- · 2019-04-29 02:53

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!

查看更多
登录 后发表回答