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 and Save data back to the database. My model classes are joined by one to one relationship.
My Models are:
public class Doctor
{
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 DoctorAvailablity DoctorAvailablity { get; set; }
}
public class DoctorAddress
{
public string Address { get; set; }
public string City { get; set; }
public int DoctorId { get; set; }
public virtual Doctor Doctor { get; set; }
}
public class DoctorCharge
{
public decimal OPDCharge { get; set; }
public decimal IPDCharge { get; set; }
public int DoctorId { get; set; }
public virtual Doctor Doctor { get; set; }
}
My ViewModel is:
public class DoctorViewModel
{
public Doctor Doctor { get; set; }
public DoctorAddress DoctorAddress { get; set; }
public DoctorCharge DoctorCharge { get; set; }
}
My Controller is:
public ActionResult Index()
{
var model = from t1 in db.Doctors
join d in db.DoctorAddress on t1.DoctorId equals d.DoctorId into listi
join dc in db.DoctorCharges on t1.DoctorId equals dc.DoctorId into listj
from d in listi.DefaultIfEmpty()
from dc in listj.DefaultIfEmpty()
select new DoctorDetailsViewModel.DoctorViewModel { Doctor = t1, DoctorAddress = d, DoctorCharge = dc };
return View(model.ToList());
}
My View is:
@model XXX.DoctorDetailsViewModel.DoctorViewModel
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Doctor</legend>
<div class="editor-label">
Name
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Doctor.Name)
</div>
<div class="editor-label">
OPD Charge
</div>
<div class="editor-field">
@Html.EditorFor(model => model.DoctorCharge.OPDCharge)
</div>
<div class="editor-label">
Address
</div>
<div class="editor-field">
@Html.EditorFor(model => model.DoctorAddress.Address)
</div> <p>
<input type="submit" value="Create" />
</p>
</fieldset>}
My Controller Class is:
public ActionResult Create()
{
return View();
}
//
// POST: /Doctor/Create
[HttpPost]
public ActionResult Create(Doctor doctor)
{
if (ModelState.IsValid)
{
db.Doctors.Add(doctor);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(doctor);
}
Please help me how do i do. Thanks in advance.
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);
}
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);
}
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.