I have a ViewModel which is joined by three Entities to get data from all entities into one view form. Although i succeeded to implement the same. But i have no idea how to Edit
First of all, it's really good that you are using ViewModels
but for this particular case, it's probably not necessary, your Create
view could look like this:
@model MvcApplication1.Models.Doctor
//other fields here
<div class="editor-label">
@Html.LabelFor(model => model.DoctorAddress.Address)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.DoctorAddress.Address)
@Html.ValidationMessageFor(model => model.DoctorAddress.Address)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.DoctorCharge.IPDCharge)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.DoctorCharge.IPDCharge)
@Html.ValidationMessageFor(model => model.DoctorCharge.IPDCharge)
</div>
//other fields here
Then your Doctor
controller:
[HttpPost]
public ActionResult Create(Doctor doctor)
{
if (ModelState.IsValid)
{
db.Doctors.Add(doctor);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
Your `Edit` action could then look like this:
[HttpGet]
public ActionResult Edit(int id = 0)
{
Doctor doctor = db.Doctors.Find(id);
if (doctor == null)
{
return HttpNotFound();
}
return View(doctor);
}
[HttpPost]
public ActionResult Edit(Doctor doctor)
{
if (ModelState.IsValid)
{
db.Entry(doctor).State = EntityState.Modified;
db.Entry(doctor.DoctorAddress).State = EntityState.Modified;
db.Entry(doctor.DoctorCharge).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
If you want to keep your ViewModel then it could look like this:
[HttpPost]
public ActionResult Edit(DoctorViewModel doctorViewModel)
{
if (ModelState.IsValid)
{
var doctorAddress = doctorViewModel.DoctorAddress;
var doctorCharge = doctorViewModel.DoctorCharge;
var doctor = doctorViewModel.Doctor;
db.Entry(doctorAddress).State = EntityState.Modified;
db.Entry(doctorCharge).State = EntityState.Modified;
db.Entry(doctor).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
Here, for creating:
[HttpPost]
public ActionResult Create(DoctorViewModel model)
{
if (ModelState.IsValid)
{
model.Doctor.DoctorAddress = model.DoctorAddress;
model.Doctor.DoctorCharge = model.DoctorCharge;
db.Doctors.Add(doctor);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
For this answer I'm using Tom Dykstra’s Tutorial Guide on Implementing Basic CRUD Functionality with the Entity Framework in ASP.NET MVC Application in ViewModel context.
The tutorial uses TryUpdateModel(TModel, String, String[]) method call to update a single Student
model in Edit
method.
var studentToUpdate = db.Students.Find(id);
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" })) {
//Save all changes made in this context to the underlying database
db.SaveChanges();
return RedirectToAction("Index");
}
TryUpdateModel
method returns true
if update was successful, otherwise it returns false
. Above the first parameter for TryUpdateModel
method call is the Student
model (studentToUpdate
). The 2nd parameter is a prefix for looking values in the value provider (empty string ""
). 3rd parameter is list of properties that are updated: ”LastName", "FirstMidName", "EnrollmentDate"
As a best practice to prevent overposting, the fields that you want to be updateable by the Edit page are whitelisted in the TryUpdateModel parameters.
To make the above work for DoctorViewModel
, the second parameter (prefix) needs to be used too. For example for Doctor
model:
Doctor doctorToUpdate = db.Doctors.Find(id);
bool doctorUpdateSuccess = TryUpdateModel(doctorToUpdate, "Doctor",
new string[] { "Name", "Speciality" });
The prefix "Doctor"
is needed for TryUpdateModel
method call, because when DoctorViewModel
is used, it cannot find Doctor
model’s parameters otherwise. For example the below Watch window shows how the form values are shown in Visual Studio in debug mode for edit method:
in Edit
view the code below:
<div class="editor-field">
@Html.EditorFor(model => model.Doctor.Name)
</div>
creates the following html:
<div class="editor-field">
<input class="text-box single-line" id="Doctor_Name"
name="Doctor.Name" type="text" value="Foo"/>
</div>
Here’s code for Edit
method for DoctorViewModel
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Doctor doctorToUpdate = db.Doctors.Find(id);
if (doctorToUpdate == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
bool doctorUpdateSuccess = TryUpdateModel(doctorToUpdate, "Doctor", new string[] { "Name", "Speciality" });
DoctorAddress doctorAddressToUpdate = doctorToUpdate.DoctorAddress;
if (doctorAddressToUpdate == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
bool doctorAddressUpdateSuccess = TryUpdateModel(doctorAddressToUpdate, "DoctorAddress", new string[] { "Address" });
DoctorCharge doctorChargeToUpdate = doctorToUpdate.DoctorCharge;
if (doctorChargeToUpdate == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
bool doctorChargeUpdateSuccess = TryUpdateModel(doctorChargeToUpdate, "DoctorCharge", new string[] { "OPDCharge" });
// if all models have been successfully updated
// then save changes to database
if (doctorUpdateSuccess &&
doctorAddressUpdateSuccess &&
doctorChargeUpdateSuccess)
{
db.SaveChanges();
return RedirectToAction("Index");
}
DoctorDetailsViewModel.DoctorViewModel viewModel = new DoctorDetailsViewModel.DoctorViewModel();
viewModel.Doctor = doctorToUpdate;
viewModel.DoctorAddress = doctorAddressToUpdate;
viewModel.DoctorCharge = doctorChargeToUpdate;
return View(viewModel);
}
It’s also a good idea to add ValidateAntiForgeryToken attribute to the code to prevent cross-site request forgery.
Update
I also made some small changes to model classes by adding attributes. This enables models with relationship to be found more easily:
DoctorAddress doctorAddressToUpdate = doctorToUpdate.DoctorAddress;
DoctorCharge doctorChargeToUpdate = doctorToUpdate.DoctorCharge;
Therefore models below have [Key]
or [Key, ForeignKey("Doctor")]
attributes
public class Doctor
{
[Key]
public int DoctorId { get; set; }
public string Name { get; set; }
public string Speciality { get; set; }
public virtual DoctorAddress DoctorAddress { get; set; }
public virtual DoctorCharge DoctorCharge { get; set; }
public virtual DoctorAvailability DoctorAvailablity { get; set; }
}
public class DoctorAddress
{
public string Address { get; set; }
public string City { get; set; }
[Key, ForeignKey("Doctor")]
public int DoctorId { get; set; }
public virtual Doctor Doctor { get; set; }
}
public class DoctorCharge
{
public decimal OPDCharge { get; set; }
public decimal IPDCharge { get; set; }
[Key, ForeignKey("Doctor")]
public int DoctorId { get; set; }
public virtual Doctor Doctor { get; set; }
}
Any feedback in relation ViewModel updates is welcomed. Recently I faced similar problem in my own project, and this was the approach I used to solve this issue. I guess there are alternative ways to handle this issue.