Robbie Mills
Robbie Mills

Reputation: 2945

ViewModel loses data on post

I'm trying to create a complex ViewModel that has both an Index Page and Create Page of Company Notes all within the Details Page of a Company, and would like some guidance as to whether I'm doing this properly.

My problem at the moment is that when I create a new Company Note, it doesn't have any information in the object beyond the EditorFor fields I include in my cshtml - it loses all the data in the ViewModel.

I have a Company model and CompanyController, and in my Details action, I populate all the notes that are relevant to the company, and a form to allow users to add a new note.

My Company and CompanyNotes model are very simple:

public class Company
{
    public int CompanyID { get; set; }
    // bunch of fields related to the company

    public virtual ICollection<CompanyNote> CompanyNotes { get; set; }
}


public class CompanyNote
{
    public int CompanyNoteID { get; set; }
    public DateTime Date { get; set; }
    public string Note { get; set; }

    public Company Company { get; set; }
}

I have a ViewModel that looks like this:

public class CompanyViewModel
{
    public Company Company { get; set; }

    // List of all notes associated with this company
    public IEnumerable<CompanyNote> CompanyNotes { get; set; }

    // A CompanyNote object to allow me to create a new note:
    public CompanyNote CompanyNote { get; set; }
}

This is my Details action, which populates the company record, gets a list of related notes, and displays a create form with a new, empty object:

    public ActionResult Details(int id = 0)
    {
        var viewModel = new CompanyViewModel();

        viewModel.Company = db.Companies.Find(id);

        if (viewModel.Company == null)
        {
            return HttpNotFound();
        }

        viewModel.CompanyNotes = (from a in db.CompanyNotes
                                  where a.Company.CompanyID.Equals(id)
                                  select a).OrderBy(x => x.Date);

        viewModel.CompanyNote = new CompanyNote
            {
                Date = System.DateTime.Now,
                Company = viewModel.Company
            };

        return View(viewModel);
    }

This is my CreateNote action in my CompanyController. (Should I split this out into a separate partial view? What would be the benefit?)

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult CreateNote(CompanyViewModel companyViewModel)
    {
        CompanyNote companyNote = companyViewModel.CompanyNote;

        if (ModelState.IsValid)
        {

            db.CompanyNotes.Add(companyNote);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(companyViewModel);

    }

Finally, here's a simplified version of detail.cshtml:

@model Project.ViewModels.CompanyViewModel

// My company detail display is here, removed for sake of berevity 
@using (Html.BeginForm("CreateNote", "Company"))
{

    @Html.EditorFor(model => model.CompanyNote.Date)
    @Html.TextAreaFor(model => model.CompanyNote.Note})
    <input type="submit" value="Create" />
}

When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.

Am I even on the right path here?

Thanks, Robbie

Upvotes: 0

Views: 1612

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038810

When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.

That's perfectly normal behavior. Only information that is included in your form as input fields is sent to the server when you submit the form and this is the only information you could ever hope the model binder be able to retrieve.

If you need the CompanyNotes collection in your HttpPost action simply query your backend, the same way you did in your GET action. You could do this by passing the company ID as a hidden field:

@Html.HiddenFor(model => model.Company.CompanyID)

So the idea is to only include as input fields in your form information that the user is supposed to somehow modify. For all the other information, well, you've already have it in your backend so all you have to do is hit it to get it.

Contrary to classic WebForms, there's no longer any notion of ViewState in ASP.NET MVC. It is much closer to the stateless nature of the HTTP protocol.

Upvotes: 7

Related Questions