Preface:
I'm working on a project using MVC 5. We've built up our database from which our model is created. Then, we've added the Controllers and Views using Scaffolding. However in the Views we've omitted some of the properties (they should not be shown to the users).
Problem:
In the actions like Edit, as I hit the save button (to update the model) I encounter an exception and I think it requires me to provide the value of all the properties.
Please let me know how can I update just some of the properties (shown in the view)?
Please note that I need a solution as general as possible, so as to be able to use it in many Edit actions that I have in this project (Indeed this is the hard part).
Code:
The following are some of the codes that I think are most related to this question:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "AreaCode,Tels,Address")] Area area)
{//Area has many more properties (defined as required in the database) and I just need to update
//these : AreaCode,Tels,Address
if (ModelState.IsValid)
{
db.Entry(area).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Detector = new SelectList(db.Detectors, "DetectorCode", "detectorName", area.Detector);
ViewBag.Grade = new SelectList(db.Grades, "Gradeid", "GradeName", area.Grade);
return View(area);
}
SS:
Answers expressed in a simple way are highly appreciated.
Get your object to update from the database, and sets your model with the old value of properties
public ActionResult Edit([Bind(Include = "AreaCode,Tels,Address")] Area area)
{
YourDbContext _db= new YourDbContext();
Area oldArea = _db.Areas.Where(x => x.ID == area.ID).FirstOrDefault();
// Bind others properties marked with required from database
area.x = oldArea.x;
area.y = oldArea.y;
if (ModelState.IsValid)
{
db.Entry(area).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Detector = new SelectList(db.Detectors, "DetectorCode", "detectorName", area.Detector);
ViewBag.Grade = new SelectList(db.Grades, "Gradeid", "GradeName", area.Grade);
return View(area);
}
Related
In Visual Studio 2012, I have Project#1 that creates and update entries in a SQL DB using EF 6.
And another Project#2 that is supposed to only select the values created/updated from Project #1
Both projects have a connection string to the local DB
Both projects reference a Data access layer library that contains the .edmx file and the generated models.
http://localhost:1535/Project1: Should create or edit an entry on
Payment entity
http://localhost:1896/Project2: Should select values from Payment
entity
The issue is: When I run both applications on different localhost ports I am able to create an entry and select it from the other project
BUT when I want to update an entry I always see the OLD value. And it is only when I rebuild Project #2 in Visual Studio 2012 that I can see the updated value.
Edit action
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Prefix = "paymentform")] PaymentForm paymentform)
{
if (ModelState.IsValid)
{
Payment payment = db.Payments.Find(paymentform.formId);
payment.paymenturl = paymentform.paymenturl;
db.Entry(payment).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(new PaymentConfig());
}
What is happening?
The solution was to avoid using a global variable that initializes the DB context
Here is what I was I doing before in Project#2
public class PayController : Controller
{
Project2Entities db = new Project2Entities();
public ActionResult Index(int? id)
{
//select
}
}
The solution was to simply use using() with the context inside.
public ActionResult Index(int? id)
{
using (var context = new Project2Entities())
{
//select here
}
}
I have no idea why declaring the context as a global variable doesn't work properly. If anyone knows please clarify.
I’m confused on how to work with updating two entities while using a ViewModel. I’ve done some searching but I’m not coming up with anything that seems to help me understand this.
I have two entities Person and Address. I use a ViewModel to combine the two together in order to display them in my Edit.cshtml. At the top of my View I declare the ViewModel to be used.
Now, I get to the actual Edit portion. I’m assuming I would have to make an update to each entity and then create a new ViewModel to return to the View since the Veiw is expecting a ViewModel?
I’m also curious if there is a way to let the View know that the update was successful so I can display a message at the top of the View or if it would be better to just redirect to the Index View.
Is this a correct way of coding this or is there a more streamlined way to accomplish the same thing?
Top of my .cshtml page
#model Project.Models.MemberViewModel
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyAccount(MemberViewModel model)
{
if (model.PersonId == 0)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
MemberViewModel updatedVM = new MemberViewModel();
using (var db = new DataContext())
{
Person currentPerson = db.Person.Find(model.PersonId);
if (currentPerson == null)
{
return HttpNotFound();
}
db.Entry(currentPerson).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
Address currentAddress = db.Address.Find(model.PersonId);
if (currentAddress == null)
{
return HttpNotFound();
}
db.Entry(currentAddress).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
////update and return a ViewModel
//updatedVM.FirstName = currentPerson.FirstName;
//updatedVM.LastName = currentPerson.LastName;
//updatedVM.Address = currentAddress.Address1;
//updatedVM.City = currentAddress.City;
//updatedVM.State = currentAddress.State;
//updatedVM.Zip = currentAddress.Zip;
}
return View(updatedVM);
}
In my opinion, the best practice would be to redirect to Index.cshtml after successfully saving the data (I think this is the whole idea of keeping MVC clean):
return RedirectToAction("Index");
I would only show an error message if there is trouble with the data.This can be done similar to this:
You can add these to the ViewModel:
bool hasErrors;
string errorMessage;
and in the view you would implement at the top of the page:
#if(Model.hasErrors)
{
<div>Model.errorMessage</div>
}
The idea is to validate the data inside the ViewModel constructor, or inside the controller, and if there is something wrong just set hasErrors = true and a custom errorMessage and show it in the view.cshtml.
Hope it helps.
I am making a page that will contains several dropdowns and a few textboxes. I have the data being stored in a viewmodel, that is then passed back to my [HttpPost] create method.
I am fairly new to viewmodels, and am wondering how to add a new database entry using data from a viewmodel.
When I go into Debug mode, all my viewmodel entities are populated, but MVC is not making the connection to make a new entry with those values. Current [Post] create method.
[HttpPost]
public ActionResult Create(OrdersViewModel vmobj)
{
if (ModelState.IsValid)
{
order obj = new order();
obj.projectNumber=vmobj.projectID.ToString();
obj.testingPhase = vmobj.testPhaseSt;
obj.unitSerial = vmobj.unitSerialSt;
obj.completeDate = vmobj.completeDate;
obj.closingStatement = vmobj.closingStatement;
db.orders.Add(obj);
return RedirectToAction("Create");
}
return View();
}
You need to call the DbContext.SaveChanges() to apply the changes in the database. Just like Jeroen told you.
I'm trying to use ViewModels and AutoMapper, as I know these are best practice for avoiding lots of issues.
I'm ok using AutoMapper to populate a viewmodel, but I'm not as sure about how to update my database, from the ViewModel being posted back to my controller.
My GET is:
public ActionResult Edit(int id = 0)
{
Customer customer = db.Customers.Find(id);
var offers = db.Offers.Where(x => x.CustomerId == id).ToList();
var email = db.Emails.FirstOrDefault();
var vm = new CreateViewModel();
vm.CustomerId = customer.CustomerId;
vm.ArrivalDate = customer.ArrivalDate;
vm.CustomerName = customer.CustomerName;
vm.EmailAddress = customer.EmailAddress;
vm.NumNights = customer.NumNights;
vm.NumPeople = customer.NumPeople;
vm.EmailBody = email.EmailBody;
vm.From = email.From;
vm.Subject = email.Subject;
// Map list of Offers into ViewModel
vm.Offers = Mapper.Map<IList<Offer>, IList<OfferVM>>(offers);
return View(vm);
}
My POST is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel vme)
{
if (ModelState.IsValid)
{
// Update the Customer properties
Customer customer = db.Customers.Find(vme.CustomerId);
customer.NumPeople = vme.NumPeople;
customer.NumNights = vme.NumNights;
customer.ArrivalDate = vme.ArrivalDate;
customer.CustomerName = vme.CustomerName;
customer.EmailAddress = vme.EmailAddress;
// Update Offers table
foreach (var o in vme.Offers)
{
// find the offer
Offer offer = db.Offers.Find(o.OfferId);
if (offer != null)
{
// update the properties of Offer
offer.RoomRate = o.RoomRate;
offer.IncludeInOffer = o.IncludeInOffer;
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vme);
}
So my Post is manually updating two database tables (Offers and Customers).
The GET method is elegant, using AutoMapper, the POST is not. I'm wondering if there is a more straightforward way of updating the database via AutoMapper, without having to manually go through each property I am looking to update? Or is my POST controller as efficient as it can be?
Thank you for any pointers,
Mark
There is an overload of Map that allows to map properties to one preexisting instance:
Mapper.Map<IObjectA, IObjectB>(objectA, ObjectB);
Thus, you just have to map from the parameter vme (CreateViewModel vme) to the customer recovered from the DB. This also applies to the offers part.
Of course, you'll have to configure the mappings in the reverse direction: you've done it from db objects to view model, and now you need to map them from viewmodel to db objects. If you've followed Automapper conventions it will be pretty easy or even unnnecessary.
EDIT: added interesting comment by Henk Mollema
You can configure AutoMapper to ignore properties, if your ViewModel doesn't contain all the properties from your domain model (which is most likely the case), it won't overwrite them with nulls.
You first need to define your mapping like this:
Mapper.CreateMap<EditViewModel, Customer>();
Your updated action method could look like this. What happens here is your action method receives your view model and checks its validity. If it is valid then it does the mapping from your view model to your domain model. This domain model is then passed to your service or repository layer to update the cusotmer in the database. How you update the record depends entirely on you.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
Customer customer = Mapper.Map<Customer>(viewModel);
customerService.Edit(customer);
return RedirectToAction("List");
}
I hope this can help guide you in the right direction. Your HttpGet action method will work in a similar way.
I'm trying to set up an MVC2 app with Entity Framework Self-Tracking-Entities. My view is a strongly-typed view of the entity type generated by the STE T4 template. Here's my GET:
public ActionResult Edit(int id)
{
var ri = new App1Service.App1Client().GetMyObj(id);
var changeTracking = ri.ChangeTracker.ChangeTrackingEnabled; // this shows true
return View(ri);
}
So far, so good. When this form POSTs, however, ChangeTracker is null / reset to default values, and thus, the STE seems suddenly to forget that it's supposed to be tracking itself:
[HttpPost]
public ActionResult Edit(MyObj ri)
{
// MyObj.ChangeTracker.ChangeTrackingEnabled now shows false
// so the following line doesn't save anything:
new App1Service.App1Client().SaveMyObj(ri);
return RedirectToAction("Index");
}
What's the secret to getting the strongly-typed-view to hang onto (and POST back) the whole STE? If I have to go and do another read to get original values and then apply changes, that seems to defeat the purpose of self-tracking-entities, doesn't it?