7

I have a complex view model that I am passing to a create view. When I enter data on the page and post it the model is empty. Both the fields in the sub-object and the "test" field are empty. Why?

public class ContactIncident
{
    [Key]
    public int Id { get; set; }

    [DataType(DataType.MultilineText)]
    public string Description { get; set; }

    [Display(Name = "Incident Date")]
    [DataType(DataType.Date)]
    public DateTime? IncidentDateTime { get; set; }

    [Display(Name = "Follow Up Date")]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
    [DataType(DataType.Date)]
    public DateTime? FollowUpDate { get; set; }
}

public class IncidentManager
{    
    public ContactIncident Incident { get; set; }
    public string Test { get; set; }
}


public ActionResult Create(int? id)
{
    IncidentManager im = new IncidentManager();
    ContactIncident ci = new ContactIncident();
    ci.IncidentDateTime = DateTime.Now;
    ci.FollowUpDate = DateTime.Now.AddDays(14);
    im.Incident = ci;
    return View(im);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IncidentManager im)
{
    if (ModelState.IsValid)
    {
        ContactIncident ci = new ContactIncident();
        ci.IncidentDateTime = incident.Incident.IncidentDateTime;
        ci.Description = im.Incident.Description;
        return RedirectToAction("Index");
    }
    return View(incident);
}

View:

@model MyApp.Web.ViewModels.IncidentManager
@{
    ViewBag.Title = "Edit Incident";
}
<h4>@ViewBag.Title</h4>    
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal well">
        @Html.ValidationSummary(true)      
        @Html.EditorFor(model=>model.Test)   
        <div class="row">
            <div class="col-md-2">
                @Html.LabelFor(model => model.Incident.IncidentDateTime)
            </div>
            <div class="col-md-2">
                @Html.DisplayFor(model => model.Incident.IncidentDateTime)
            </div>
        </div>
        <div class="row">
            <div class="col-md-2">
                @Html.LabelFor(model => model.Incident.Description)
            </div>
            <div class="col-md-10">
                @Html.EditorFor(model => model.Incident.Description, new { htmlAttributes = new { @class = "form-control", rows = "5" }, })
            </div>
                   <div class="col-md-2">
                @Html.LabelFor(model => model.Incident.FollowUpDate)
            </div>
            <div class="col-md-2">
                @Html.EditorFor(model => model.Incident.FollowUpDate, new { htmlAttributes = new { @class = "form-control"}, })
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
}
Alexander Schmidt
  • 5,631
  • 4
  • 39
  • 79
John S
  • 7,909
  • 21
  • 77
  • 145
  • 2
    can u show your cshtml? wanna see how you are defining the form elements – Bassam Mehanni Jul 24 '14 at 16:26
  • 1
    I assume `ci.IncidentDateTime = incident.Incident.IncidentDateTime;` is a typo and its really `ci.IncidentDateTime = im.Incident.IncidentDateTime;`. Ditto for `return View(incident);` being `return View(im);` (your code as is does not compile). Otherwise your code works fine although `IncidentDateTime` will be `null` because you do not create a form input for it. –  Sep 30 '17 at 22:30

1 Answers1

8

The problem is that the DefaultModelBinder won't be able to map nested models properly if you use a different parameter name. You must use the same parameter name as the ViewModel name.

public ActionResult Create(IncidentManager incidentManager)

As a general practice, always use the name of the model as the parameter name to avoid mapping problems.

UPDATE:

The DefaultModelBinder uses "convention based" mapping.

IncidentManager.Incident = incidentManager.Incident (will map)
IncidentManager.Incident = im.Incident    //won't map because 'im' != 'incidentManager'
openshac
  • 4,966
  • 5
  • 46
  • 77
Yorro
  • 11,445
  • 4
  • 37
  • 47
  • 1
    Please elaborate. Are you speaking to the class IncidentManager? If so why? – John S Jul 24 '14 at 16:54
  • 1
    I made the change and it worked. I've run into this several times before and always worked around it, but this will be much better. – John S Jul 24 '14 at 16:59
  • 3
    _You must use the same parameter name as the ViewModel name._ is **WRONG**!. The parameter name can be any valid name, so long as it not the same name as one of the properties in the model. –  Sep 30 '17 at 22:14
  • 1
    @StephenMuecke - They may have updated the model binder logic since this post. Since this is the accepted answer, this must have fixed the posters problem. Regardless of your opinion. – Yorro Oct 02 '17 at 07:05
  • 1
    No, it has no changed (its the same for MVC-3, MVC-4 and MVC-5 with respect to this) –  Oct 02 '17 at 07:07
  • @StephenMuecke - Then you took a piece of my answer and twisted it to appear wrong. The context is about nested models. – Yorro Oct 02 '17 at 07:12
  • It has nothing to do with nested models. Using say `public ActionResult Create(IncidentManager model)` will work fine. But OP's code does not even compile (see my comment to the question) –  Oct 02 '17 at 07:13