Reputation: 1370
I have a db model for which I would like to edit with multiple Views. My actual db model is much larger than what I'm showing here.
Db Model: PersonModel
public class PersonModel {
public Int32 Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Description { get; set; }
}
I then have two Views, EditPersonName.cshtml and EditPersonDescription.cshtml that enable the user to modify different parts of the PersonModel.
View 1: EditPersonName.cshtml
@model EditPersonNameModel
@using (Html.BeginForm()) {
<div class="editor-label">@Html.LabelFor(m => m.FirstName)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.FirstName)</div>
<div class="editor-label">@Html.LabelFor(m => m.LastName)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.LastName)</div>
<input type="submit" value="Save" />
}
View 2: EditPersonDescription.cshtml
@model EditPersonDescriptionModel
@using (Html.BeginForm()) {
<div class="editor-label">@Html.LabelFor(m => m.Description)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.Description)</div>
<input type="submit" value="Save" />
}
Rather than have each view tied directly to PersonModel, I'm wanting link them to EditPersonNameModel and EditPersonDescriptionModel, respectively.
View Model 1: EditPersonNameModel
public class EditPersonNameModel {
public string FirstName { get; set; }
public string LastName { get; set; }
}
View Model 2: EditPersonDescriptionModel
public class EditPersonDescriptionModel {
public string Description { get; set; }
}
I suppose it's also worth mentioning that I'm using EF's DbContext pattern.
public class PersonDbContext : DbContext {
public DbSet<PersonModel> Persons { get; set; }
}
Now for my controller, which is where things start to break!
Controller: PersonController
public class PersonController {
private PersonDbContext = new PersonDbContext();
[HttpGet]
public ActionResult EditName(int id) {
// ??? ---- #1 -----
}
[HttpPost]
public ActionResult EditName(EditPersonNameModel model, int id) {
if (ModelState.IsValid) {
// ??? ---- #2 ----- ??? Update FirstName,LastName ONLY
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
[HttpGet]
public ActionResult EditDescription(int id) {
// ??? ---- #3 -----
}
[HttpPost]
public ActionResult EditDescription(EditPersonDescriptionModel model, int id) {
if (ModelState.IsValid) {
// ??? ---- #4 ----- ??? Update Description ONLY
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
}
My problem is that I don't know what to put in the ??? ---- #X -----
blocks above. Obviously #1
and #3
are going to be very similar, as are #2
and #4
.
Some have suggested that I use AutoMapper. That helps going from the Db Model to the View Model, but not the other way around.
Presently my code block for #1
looks like this:
[HttpGet]
public ActionResult EditName(int id) {
PersonModel person = db.Persons.Find(id);
if (person == null) {
return HttpNotFound();
}
EditPersonNameModel viewModel;
AutoMapper.Mapper.CreateMap<PersonModel, EditPersonNameModel>();
viewModel = AutoMapper.Mapper.Map<PersonModel, EditPersonNameModel>(person);
return View(viewModel);
}
And my code block for #2
looks like this:
[HttpPost]
public ActionResult EditName(EditPersonNameModel viewModel, int id) {
if (ModelState.IsValid) {
PersonModel person = db.Persons.Find(id);
if (person == null) {
return HttpNotFound();
}
EditPersonNameModel viewModel;
AutoMapper.Mapper.CreateMap<EditPersonNameModel, PersonModel>();
person = AutoMapper.Mapper.Map<EditPersonNameModel, PersonModel>(viewModel);
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
#1
above actually works fine. AutoMapper will map from the larger Person object to the EditPersonName object.
#2
above, however, is broken. AutoMapper will map from EditPersonNameModel object to a new Person object and thus db.Entry(person).State = EntityState.Modified
will throw an exception:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I don't need AutoMapper to create a new object, I need to to map to an existing object. If only there were AutoMapper.Mapper.
Update<EditPersonNameModel, PersonModel>(viewModel, person)
.
Ok, so this is now a very LONG question! Thank you for reading and I really appreciate your help! If you are familiar with AutoMapper and know that I'm doing something wrong, please let me know. If you read this code and know of a different way to do this altogether, please let me know that too!
Thank you!!!
Update 1
I have a #2
working, but I'm not sure how good it is.
New #2
[HttpPost]
public ActionResult EditName(EditPersonNameModel viewModel, int id) {
if (ModelState.IsValid) {
PersonModel person = db.Persons.Find(id);
if (person == null) {
return HttpNotFound();
}
TryUpdateModel(person);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
Note that TryUpdateModel
is called on person
rather than viewModel
. I've already fetched person
from the database, so it's completely filled out, with an Id. The TryModelUpdate
method uses an IModelBinder
to map properties from the current "mvc request context" to the specified object. Since my PersonModel
and EditPersonNameModel
use the same named properties, it works. This is really a very poor solution, so if you know of something better, please let me know!
Upvotes: 1
Views: 881
Reputation: 9052
I'd recommend using Automapper or similar solution, there is no built-in support for model->model mapping in mvc 3.
Upvotes: 2