Updating an object with MVC3
I have a model that I can modify, please see the sample below:
[HttpPost]
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
db.Entry(c).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
The model has other fields that are not showing in the view and cannot be modified by the user, but when I click the submit button the fields that were not showing in the view got set to null.
Can I somehow let EF know not to modify certain fields? Thanks.
Generally it is better not to bind to the entity object directly, rather create an edit model and bind to that.
After all.. whats to stop someone posting back values you don't want changed with this approach?
The main problem here is the fact that mvc model binding changes the properties in the model before its in a context therefore the entity framework doesn't know which values have changed (and hence which should be updated)
You've mitigated that slightly with db.Entry(c).State = EntityState.Modified; but that tells the entity framework that the whole record has been updated.
I would normally do the following:
Bind to a model specifically for this controller first
Create an instance of the entity class you want to update, set the Id accordingly and attach it to the context
Update the properties on the entity to be the same as the model you binded to (object is attached and therefore entity framework is tracking which columns are being changed now)
SaveChanges
Step 3 is a bit tedious therefore consider using a tool like automapper to make things easier
Edit:
[HttpPost]
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
Company dbCompayObjct = new Company { companyId = c.companyId };
db.Company.Attach(dbCompayObjct);
dbCompanyObjct.CompanyName = c.CompanyName;
dbCompanyObjct.City = c.City;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
You are apparently overwriting your existing record with an incomplete record. When you use the method above, it will completely replace the existing one.
You either need to fill in all the fields you don't want to replace with the existing values, or you need to get the existing record and modify the fields you want to modify, then save it.
Reflection is not always evil, sometimes it's your friend:
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
Company UpdateC = db.Company.find(c.CompanyID);
foreach (var property in typeof(Company).GetProperties())
{
var propval = property.GetValue(c);
if (propval != null)
{
property.SetValue(UpdateC, propval);
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
Related
I'm quite new in MVC and can't get my head around a possible overposting threath. I have an "Event" model which contains an Id property. When a user, for example, wants to edit an existing "Event", I use this property for fetching the "Event" I need to update from a collection of "Events".
I tried to decorate the Id property with the [BindNever] or [Editable] attribute which results in an Id property of 0 as the property no longer binds after a post. This, of course, generates problems when I want to use this Id property for fetching an "Event" from the collection.
So I leave the property undecorated. But it feels unnatural as this property should not be editable by a user. Using a ViewModel does not solve my problem as an Id property would still be needed.
In all examples I find online, an Id property is always part of a binding model. Does this pose threats to possible overposting? I assume not as, when working with Entity Framework, for example, the Id property is not Editable as it is autoincremented. A user would still be able to change the Id in order to update another "Event" though but in the case of my application, this generates no issue as a user is able to edit any "event" he wants
Model:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
[HttpGet]
public IActionResult EditEvent(int? id)
{
if (id == null)
{
return NotFound();
}
else
{
var eventToEdit = _events.GetEvent(id.Value);
return View(eventToEdit);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditEvent(Event postedEvent)
{
if (ModelState.IsValid)
{
if (postedEvent == null)
{
return NotFound();
}
else
{
var eventToUpdate = _events.GetEvent(postedEvent.Id);
eventToUpdate = _events.EditEvent(postedEvent, eventToUpdate);
return RedirectToAction(nameof(EventDetails), new { id = eventToUpdate.Id });
}
}
else
{
return View(postedEvent);
}
}
Does ID property in binding model pose a threat to overposting?
Yes, if you're using this id as a lookup for editing then you need to write a check that ensures that the user is allowed to perform the edit on the data.
public IActionResult EditEvent(Event postedEvent)
{
//Make sure the current user can edit the posted event
if(!CanUserEditEvent(postedEvent.Id, User.GetUserId()) return Forbid();
//User can edit this event so continue normally
if(ModelState.IsValid)
...
}
Furthermore, ensure that other "read-only" or unused properties of your model that are exposed on the view and action are not then used as a lookup or saved. A view model can help with this by allowing you to define a subset of your domain model properties.
I have a situation where i have to take input(form) from user. After continue button is pressed next view page is displayed. But after continue is pressed i don't want to store the model in the DB. I have to display some details(combining some tables) according to input given by the user earlier and again get some data from user. Only then i want to store the model in the respective tables.
How can i perform this? I tried getting Model from user and passing to the function that generates next page. Is this is way to do it? or there is other way around?
Store the model submitted by the first form in session.
[HttpPost]
public ActionResult ContinueForm1(Model1 model1)
{
if(ModelState.IsValid)
{
Session["Model1"] = model1;
return View("Form2");
}
return View();
}
[HttpPost]
public ActionResult ContinueForm2(Model2 model2)
{
if(ModelState.IsValid)
{
... model2 is already here, get the model1 from session
... and save to datatbase finally return a different view or redirect to some
... other action
}
return View();
}
You are heading down the right track.
You need to grab the model that is passed back from the first view - preferably you are using ViewModels here rather than binding directly to your db models. Have a look at http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/ and Why should I use view models? as to why these are good things.
The easiest way to do this is to pass the model in as an argument to your method e.g.
Assuming that your views are using the same ViewModel ( which may or may not be true) then you can send the viewmodel straight to your new view - else you can copy the elements into a new viewModel and send that.
e.g.
[HttpPost]
public ViewResult Step1(MyViewModel viewModel)
{
//Do some validation here perhaps
MySecondViewModel secondViewModel = new MySecondViewModel{
Id = viewModel.Id,
// etc. etc.
};
return View("Step2", secondViewModel);
}
Then you can carry on as you need until you have to persist the entity to the database.
NB as you do not need to do anything special in the form to make it post the model as an argument as long as the view is strongly typed to that ViewModel.
I have an model being passed back successfully from my view except that I would like an ID value to be the first thing that is bound back to the Model so it can fill some information from the db. The information being passed back is as follows
SetupID:91c16e34-cf7d-e111-9b66-d067e53b2ed6
SwayBarLinkLengthLF:
SwayBarLinkLengthRF:
.....way more information....
My Action is as follows
[HttpPostAttribute]
public ActionResult SaveSetup(SetupAggregate setup)
{
setup.SaveSetup();
return null;
}
I would like the SetupID to be the first property that is set on the empty setup object but it looks like the first property alphabetically is being set first.
In my opinion you really need to be working with 2 separate "models" - your ViewModel, which is what is in your MVC project and is rendered to your view. And a 2nd EntityModel in a business logic layer. This is standard "enterprise" programming design. It gives you a lot more control of your data. The idea is this.
UI Assembly (MVC project)
ViewModel definition
public class MyModel {
public int ID { get; set; }
.... // bunch of other properties
}
Controller
public class InterestingController : Controller {
public ActionResult CreateNewWidget() {
var model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult CreateNewWidget(MyModel model) {
if(ModelState.IsValid) {
// your ctor can define the order of your properties being sent in and you can set the entity values in the ctor body however you choose to. Note never SET an ID/Primary key on a Create, let the DB handle that. If you need to return the new Key value, get it from the insert proc method in your DAL and return it up the stack
var entityModel = new EntityFromBLL(model.Name, model.OtherProperty, ... etc);
entityModel.Save(User.Identity.Name); // your save method should always capture WHO is doing the action
}
return View(model);
}
public ActionResult UpdateExistingWidget(int id) {
var entityModel = new EntityFromBLL(id); // get the existing entity from the DB
var model = new MyModel(entityModel.ID, entityModel.Name, ... etc); // populate your ViewModel with your EntityModel data in the ViewModel ctor - note remember to also create a parameterless default ctor in your ViewModel as well anytime you create a ctor in a ViewModel that accepts parameters
return View(model);
}
[HttpPost]
public ActionResult UpdateExistingWidget(MyModel model) {
if(ModelState.IsValid) {
var entityModel = new EntityFromBLL(model.ID); // always pull back your original data from the DB, in case you deal with concurrency issues
// now go thru and update the EntityModel with your new ViewModel data
entityModel.Name = model.Name;
//... etc set all the rest of the properties
// then call the save
entityModel.Save(User.Identity.Name);
}
return View(model)
}
}
Your Entity Model should be defined with private fields, public properties, a ctor that takes in all required fields for an insert (minus the primary key), a ctor that takes in the primary key and then can call an internal load method statically to return a populated object. Business rules and property validation and a single Save method. The Save method should check for the IsDirty bit after all the properties have been set and call the respective Insert or Update methods .. which in turn should call into a DAL passing DTOs
would like to put fieldSets side-by-side on my “Edit” page because I have so many fields on the page. Since I couldn’t find an easy fix, I decided to put the fields in a table. This worked fine except for when I click on the “Save” button I get this error:
“Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.”
Questions: How can I put my fieldsets side-by-side or make my table to work with the save button?
Thanks for any help.
Here's the edit methods of my controller:
public ActionResult Edit(int id)
{
CourseProgress courseprogress = db.CourseProgresses.Find(id);
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name", courseprogress.CourseId);
ViewBag.TeacherId = new SelectList(db.Teachers, "TeacherId", "Name", courseprogress.TeacherId);
var PdfReportProperties = new PdfReport();
return View(courseprogress);
}
//
// POST: /ProgressManager/Edit/5
[HttpPost]
public ActionResult Edit(CourseProgress courseprogress)
{
if (ModelState.IsValid)
{
db.Entry(courseprogress).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("ProgressRecord");
}
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name", courseprogress.CourseId);
ViewBag.TeacherId = new SelectList(db.Teachers, "TeacherId", "Name", courseprogress.TeacherId);
return View(courseprogress);
}
The most likely reason you are getting that error is that as you don't have a field for the ID of your model in the form, once you hit the SAVE button, the object you are editting has its ID property in null.
To solve it, use a hidden field holding the ID of the model, so that once the form post its fields it became mapped in your controller model object.
This problem has nothing to do with the position or layout of your fields.
I've been trying to add a new record.
public ActionResult Create()
{
var dc = new ServicesDataContext();
ViewData["CustomerID"] = TempData["CustomerID"];
var a = dc.services.Select(arg => arg.ServiceID).ToList();
ViewData["ServiceID"] = new SelectList(a);
var model = new Maping();
return View(model);
}
//
// POST: /Customerservice/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude ="CustomerServiceMappingID")] Maping serviceToCreate)
{
if (!ModelState.IsValid)
return View();
var dc = new ServicesDataContext();
dc.Mapings.InsertOnSubmit(serviceToCreate);
dc.SubmitChanges();
return RedirectToAction("Index", "Home");
}
Now the situation is that the tempdata has the correct value but by the time i submit changes the customerID turns out to be null. So, kindly help me in solving this.
AFAI understand you copy the customer id from TempData to ViewData. However, the contents of the ViewData will be not preserved after the request ends. In your view you should put the customer id into an input (e.g. a hidden field if it should be not displayed) to get it back in your post action.
If you use a strongly typed model, you should not use the ViewData at all, but rather you should set the customer id on the model instance. Then in the view you could use a Html.HiddenFor(m => m.CustomerId) to "preserve" this id.
I hope I did not misunderstand the question, unfortunately it is not really visible in your code snippets where you would have a customer id in the post action that is null.