How to delete an item from database without actually deleting it? - c#

I have been working on an ASP.NET MVC 5 web application using Entity Framework 6 as an assignment for my Business Programming II class. Despite the fact that I know very little about programming, I have been making progress, but I have run into trouble. I am supposed to write CRUD operations for an online storefront based on the Northwind Traders database. I already have working code for reading from the database as well as adding and updating items in the database. Where I'm struggling is deleting items. The following requirement is listed in the assignment description:
Delete a product by making it discontinued so that the information is displayed in the database. Do NOT actually delete a product from the database.
I've tried a couple things to try and make this work, but all have failed for various reasons.
Here's the code to my current Delete View (ignore any strange HTML formatting decisions, right now I'm focused on getting this functional):
#model NWTradersWeb.Models.Product
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product: #Html.DisplayFor(model => model.ProductName)</h4>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-actions">
<input type="submit" value="Yes" class="btn btn-dark" /> |
#Html.ActionLink("Back to List", "Index")
</div>
}
</div>
I have tried editing my ProductsController.cs to manually set the Discontinued attribute to true as follows:
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Product product = db.Products.Find(id);
product.Discontinued = true;
db.SaveChanges();
return RedirectToAction("Index");
}
This works, but if I run the Edit operation on the same product I'm unable to undo the change. I can deselect the Discontinued checkbox but it does not save after I submit the changes and the Index page still shows the product as discontinued.
Here's my code for the Edit View and corresponding ProductsController.cs methods, I'm unsure if these have anything to do with my problem but I will include them anyway:
View:
#model NWTradersWeb.Models.Product
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product: #Html.DisplayFor(model => model.ProductName)</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ProductID)
<div class="form-group">
#Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SupplierID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SupplierID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CategoryID, "CategoryID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CategoryID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CategoryID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.QuantityPerUnit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuantityPerUnit, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuantityPerUnit, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitPrice, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitPrice, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitPrice, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitsInStock, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitsInStock, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitsInStock, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitsOnOrder, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UnitsOnOrder, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UnitsOnOrder, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ReorderLevel, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ReorderLevel, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ReorderLevel, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Discontinued, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.Discontinued)
#Html.ValidationMessageFor(model => model.Discontinued, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Controller Methods:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued")] Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
My professor also alluded to making the Delete operation redirect to a simpler Edit page where we could toggle the Discontinued attribute. I think he may be alluding to a partial view but we have not covered that to my knowledge.
Please note: I consider myself a novice when it comes to programming. I've taken other classes but the instructors focused more on syntax than concepts and as such my foundation is incredibly weak. I might be clueless about certain things that other people take for granted. I want to go back and study the fundamentals after I graduate and self-study, but this is a required class for a degree that is almost completely unrelated to programming. Any tips, hints, even a nudge in the right direction would be greatly appreciated.

Your Delete logic seems fine. What I would look at in more detail is your Edit.
Overall I am not a fan of ever passing Entities between the server and the view, especially accepting an entity from the view. This is generally a bad practice because you are trusting the data coming from the view, which can easily be tampered with. The same when passing data from a view to server, this can lead to accidentally exposing more information about your domain (and performance issues) just by having some "sloppy" JavaScript or such converting the model into a JSON model to inspect client-side. The recent case of the journalist being accused of "hacking" because they found extra information via the browser debugger in a Missouri government website outlines the kind of nonsense that can come up when server-side code has the potential to send far too much detail to a browser.
In any case, in your Edit method when you accept the bound Product after deactivating the Discontinued flag, what values are in that Entity model? For instance if you use Delete to set Discontinued to "True", then go to the Edit view for that product and un-check that input control and submit the form, in your "product" coming in the Edit page, what is the state of the product.Discontinued?
If the value is still "True" then there is a potential problem with your page binding where the EditorFor is not linking to that flag properly or the value is not deserializing into the Product entity. (a private or missing setter?)
If it is coming back with what should be the correct value, then I would look at changing how you update entities. Code like this:
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
... is inherently dangerous as "product" is not an entity, it is a deserialized set of values used to populate an entity class. Ideally when updating data you would provide a ViewModel that won't be confused with an Entity class and contain just the fields that are allowed to be updated. Using your current code though with the entity class serving as that view model I would suggest something more like:
var dataProduct = db.Products.Single(x => x.Id == product.Id);
dataProduct.ProductName = product.ProductName;
dataProduct.Discontinued = product.Discontinued;
// ...
db.SaveChanges();
When it comes to possibly allowing the user to change FKs for things like categories, then you should eager load those relationships, compared the FK IDs then load and re-associate those "new" relationships in the entity loaded from data state. (Don't just replace the FK values.)
The reason for doing this rather than attaching and setting the state to modified:
We perform a validation when loading the entity. If we get back an Id that doesn't exist, we can handle that exception. We can also filter data to ensure that the current user actually has permission to see the requested ID and can end a session if it looks like someone is tampering with data.
We only update values that we allow to change, not everything in the entity. We can also validate to ensure that the values provided are fit for purpose before making changes.
When copying values across, EF will only generate UPDATE statements for values that actually change if any actually change. Attaching and setting the entity state to Modified or using Update will always generate an UPDATE statement replacing all values whether anything changed or not. (can have negative impacts on triggers or hooks in the DbContext for things like Auditing)

Related

Controller is ignoring data changes?

I'm having some trouble with data being sent through my controller, here's a simplified example:
public ActionResult EditNote(NotesModel model)
{
model.Author = Session["UserName"].ToString();
model.Note = null;
model.Title = null;
return View(model);
}
On my views page the data shown from the model is the exact same as how it was received by the method and all changes are ignored, why?
Bigger picture:
I'm trying to have a user edit an existing note in the database, if they're the one who made it of course. based on whether or not they're the author they will either edit the existing note or create a new note, this is where the problem lies. The controller is supposed to set all the values of the model to null so that on the views page they will be empty.
Editing an existing note is no problem however emptying the model so the editing page is blank does not work.
EDIT
This is my view page:
#model WebsiteProject.Models.NotesModel
#{
ViewBag.Title = "";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#section Sidebar
{
<div id="sidebarheadericon" style="background-image: url('../Content/icons/apps.png')"></div>
<div id="headertext"><h1>Welcome</h1></div>
<hr id="seperator" />
<p class="psidebar">test</p>
<p>
#Html.ActionLink("Create New", "EditNote")
</p>
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h1>NotesModel</h1>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<p class="control-label">Note by #Session["UserName"].ToString()</p>
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Note, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Note, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Note, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="largebtn" />
</div>
</div>
<p class="text-danger">#ViewBag.NoteViewError</p>
</div>
}
<div>
#Html.ActionLink("Back to List", "NoteApp")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Here you can see the data that is received (dummy data)
Now here you'll see that the data of the model is changed
Yet on the website it is not changed
The biggest problem isn't the Note and Title not being changed because the user can do that, but the Id and Author, which the user cannot, and should not be able to change.
This is related to the fact that you are using EditorFor:
#Html.EditorFor(model => model.Note, new { htmlAttributes = ... })
It so happens that EditorFor not only uses the Model object, it also uses ModelState which keeps a copy of all values for all Model items that were received, parsed and validated, along with any validation errors that this may have produced.
As long as ModelState has a value for that model item, it will be shown by EditorFor. The reason is that user input may be invalid, like entering 12X for an int Model property. EditorFor will show 12X if the form is re-rendered, which is coming from ModelState, and which could never come from Model.
Unwanted values can be removed by calling Remove, like this:
ModelState.Remove("Note");
On a different note, the ViewModel class that you are using here is not suited to the needs of this action method. It may be better to create a dedicated ViewModel class for it, with fewer properties, which you can then convert to the ViewModel type that the View needs.
I think you want to tell the incoming notes model is a new one or an existing one, here is some code to try,
public ActionResult EditNote(NotesModel model)
{
if(model.Id > 0) //assuming existing notes has id or any other ways you want to check
{
//save data
return View(model);
}
else //if Id has a value <= 0, return a new model with only Author set, maybe the Id (depending on how you want to generate the Id)
{
var model = new NotesModel();
model.Author = Session["UserName"].ToString();
return Viwe(model);
}
}

The UPDATE statement conflicted with the FOREIGN KEY constraint MVC Web Application

Context:
I want a user to only update one part [AppStatus] of an entry. However, when I test it, I get the error mentioned in the subject. I've seen similar questions asked, and tried doing some of the steps they outlined (using CASCADE for the FK, for instance). But it didn't work. So it's probably something in the code that I messed up with. I thought maybe, if I adjust the Bind to only include [AppStatus] that would do it, but that didn't work either.
The error throws on two different FKs [StudentID] and [JobPostingID].
Code:
Controller:
public ActionResult Edit([Bind(Include = "ApplicationID,StudentID,JobPostingID,Resume,AppStatus")] Application application)
{
if (ModelState.IsValid)
{
db.Entry(application).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.StudentID = new SelectList(db.Students, "StudentID", "FirstName", application.StudentID);
ViewBag.JobPostingID = new SelectList(db.JobPostings, "JobPostingID", "Position", application.JobPostingID);
return View(application);
}
View:
<div class="form-horizontal">
<h4>Application</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ApplicationID)
<div class="form-group">
#Html.LabelFor(model => model.Student.FirstName, "FirstName", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.Student.FirstName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.JobPosting.Position, "Position", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.JobPosting.Position)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AppStatus, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(model => model.AppStatus, "--Update Application Status--", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.AppStatus, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
I usually use this syntax, especially if I want to update only certain properties
public ActionResult Edit( Application application)
{
if (ModelState.IsValid)
{
var existedApplication=db.Applications.FirstOrDefault(i=> i.ApplicationID=application.ApplicationID);
if(existedApplication!=null)
{
existedApplication.AppStatus = application.ApplicationStatus;
db.Entry(existedApplication).Property(i => i.ApplicationStatus).IsModified = true;
var result = db.SaveChanges();
}
.....
Serge's answer pretty much fixed my issue- but I did need to make a couple of adjustments to get it to run.
public ActionResult Edit(Application application)
{
if (ModelState.IsValid)
{
var existedApplication = db.Applications.FirstOrDefault(i => i.ApplicationID == application.ApplicationID);
if (existedApplication != null)
{
existedApplication.AppStatus = application.AppStatus;
db.Entry(existedApplication).Property(i => i.AppStatus).IsModified = true;
var result = db.SaveChanges();
return RedirectToAction("Index");
}
return View(application);
}
return View(application);
}

Scaffolded model/view: The ViewData item that has the key 'COLUMN' is of type 'System.String' but must be of type 'IEnumerable<SelectListItem>'

I know that there have been a number of questions asked similar to this, but they all seem to be dealing with a page that loads fine and has problems when trying to POST. I'm using scaffolded code in an MVC project with a code first database. The dropdown that's failing is supposed to be filled from a foreign key relationship, there are two other dropdowns using exactly the same code but with different names, and commenting shows that only this one dropdown is bad. This should be on an edit but the page doesn't even load to be submitting data like the other questions do. Both the create and the edit pages do not load.
With all that said, this is the controller:
public ActionResult Edit(long? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.People.Find(id);
if (person == null)
{
return HttpNotFound();
}
ViewBag.Title = new SelectList(db.Job_Title, "Id", "Title", person.Title);
ViewBag.Location = new SelectList(db.Locations, "Id", "Name", person.Location);
ViewBag.Manager = new SelectList(db.Managers, "Id", "Name", person.Manager);
return View(person);
}
and this is a section of the corresponding view:
<div class="form-group">
#Html.LabelFor(model => model.Title, "Title", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*FIXME: Commenting the line below will allow the page to load fine*#
#Html.DropDownList("Title", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Location, "Location", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Location", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Location, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Manager, "Manager", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Manager", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Manager, "", new { #class = "text-danger" })
</div>
</div>
I'd love to know why just the title dropdown kills the page while the other 2 are completely fine. I'd also love to know why scaffolded code was generated with an error!
The error means that the value of ViewBag.Title is null or not an IEnumerable<SelectListItem>.
This will be happening because your view includes
#{
ViewBag.Title = ".....";
#
which overwrites the value you set in the GET method.

How to auto fill fields in a form using #Html.EditorFor in ASP.net MVC 5?

I'm using an autogenerated form from visual studio for asp.net mvc 5 that saves information to a database. It's the create view with the standard scaffolding etc from asp.net with entity framework.
I like the way the form looks, but I need one field (datecreated) to at least be auto filled, (but preferably autofilled and hidden). The problem is I don't understand the autogenerated code at all and my efforts to look it up have not been successful. Nor have my efforts to understand it. I'm still a beginner with html helpers, which I think these are.
Here is the form element I am working with. The part in the middle is the part I need to change to autofill (the date created field), I think the relevant part is changing the EditorFor. but I don't know:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>New Patient:</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
... //other form items removed for simplicity
<div class="form-group">
#Html.LabelFor(model => model.DateCreated,"Date Created", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateCreated, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DateCreated, "", new { #class = "text-danger" })
</div>
</div>
... //more items left out for simplicity
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
And the auto generated controller for this part looks like this:
// GET: Subjects/Create
public ActionResult Create()
{
return View();
}
// POST: Subjects/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,DOB,Male,Female,Address,City,ZIP,PhoneHome,PhoneCell,Email,EmergencyContact,EmergencyContactPhone,EmergencyContactRelationship,ReferredBy,DateCreated,Allergy,AllergyDescription,HighBloodPressure,LowBloodPressure,HeartCondition,Diabetes,Anemia,HighCholesterol,Pacemaker,Epilepsy,Pregnant,Cancer,STD,Pain,PainDescription,Headache,HeadacheDescription,CommonCold,HighBloodPressureConcern,Stress,Depression,Sleep,Menstruation,Fertility,WeightControl,Other")] Subject subject)
{
if (ModelState.IsValid)
{
db.SubjectDatabase.Add(subject);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(subject);
}
If you dont know how I can autofill and or hide the form element datecreated, could you please point me to where I might learn to figure this out myself. I think I am reasonable at programming, I just don't understand html helpers well, or the bind function in the controller.
Remove this part from your View
<div class="form-group">
#Html.LabelFor(model => model.DateCreated,"Date Created", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateCreated, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DateCreated, "", new { #class = "text-danger" })
</div>
</div>
And then, inside your Controller remove DateCreated from Bind attribute and assign DateCreated property:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,DOB,Male,Female,Address,City,ZIP,PhoneHome,PhoneCell,Email,EmergencyContact,EmergencyContactPhone,EmergencyContactRelationship,ReferredBy,Allergy,AllergyDescription,HighBloodPressure,LowBloodPressure,HeartCondition,Diabetes,Anemia,HighCholesterol,Pacemaker,Epilepsy,Pregnant,Cancer,STD,Pain,PainDescription,Headache,HeadacheDescription,CommonCold,HighBloodPressureConcern,Stress,Depression,Sleep,Menstruation,Fertility,WeightControl,Other")] Subject subject)
{
if (ModelState.IsValid)
{
subject.DateCreated = DateTime.Now; //if you want UTC time, use DateTime.UtcNow
db.SubjectDatabase.Add(subject);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(subject);
}
The #Value can also be used to prefill in spots by using #Html.EditorFor:
Example:
#Html.EditorFor(c => c.Propertyname, new { #Value = "5" })
There is more information to be found at:
Html.EditorFor Set Default Value

Model not populating on POST

I created a simple class to test this directly, on the Edit POST action I was under the impression if a property is not in the form it will use the value which exists in the database already, instead it is getting set to null and overwriting any values.
This is from the scaffold generation, lets say I comment out Password and Password Confirmation because I don't want them to edit it, so they will get left out of the POST values.
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Email, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
#*<div class="form-group">
#Html.LabelFor(model => model.Password, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PasswordConfirmation, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PasswordConfirmation)
#Html.ValidationMessageFor(model => model.PasswordConfirmation)
</div>
</div>*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
So the request would look like this:
__RequestVerificationToken=wcxt2SbPmLuI-FzGh7b0okDgfFIXacVKnvvuYpVkgJjpNEAgCbMzHTeMQv61xhbxch0kId6nh6mK-qoKML3CHpLOfk1SawIQIpdtVicWkys1&ID=1&Email=foo.bar%40test.com&FirstName=foo&LastName=bar
And here I simply want to save the values that were edited and any values which were not included use the original values instead.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Email,FirstName,LastName,Password,PasswordConfirmation")] User user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
This is just an example to help me understand how to object gets populated when it's not in the form, if anyone could elaborate or point to resources which clearly show how the model is populated when the values aren't explicitly in the POST request that would be awesome.
I was under the impression if a property is not in the form it will
use the value which exists in the database already
That is not true. How does the form (just some HTML in the client browser) knows what value you have in the database ?
You have 2 solutions
1) In your HttpGet method, Send all the properties of your entity to the form, but don't display those to user. Keep in hidden fields inside the form and when you post the form, It will be available.
public ActionResult Edit(int id)
{
var vm = new EditUserVM();
var user = GetUserFromYourDb(id);
vm.Name = user.Name
vm.Email= user.Email
//The below property you don't want user to edit in form. But still sending.
vm.Token= user.Token
return View(vm);
}
in your view
#model EditUserVM
#using(Html.Beginform())
{
#Html.TextBoxfor(s=>s.Name)
#Html.TextBoxfor(s=>s.Email)
<!-- Hidden field here -->
#Html.HiddenFor(s=>s.Token)
#Html.HiddenFor(s=>s.UserId)
<input type="submit" />
}
2) If you do not wish to send all the fields to form, You may try this.(I recommend this). Have only fields which you want the user to edit in your GET form and In the HttpPost action method, Read the existing entity again from the database and update the fields with the values you received from the form. Your other field values stays same.
public ActionResult Edit(int id)
{
var vm = new EditUserVM();
var user = GetUserFromYourDb(id);
vm.Name = user.Name
vm.Email= user.Email
return View(vm);
}
in your view
#model EditUserVM
#using(Html.Beginform())
{
#Html.TextBoxfor(s=>s.Name)
#Html.TextBoxfor(s=>s.Email)
#Html.HiddenFor(s=>s.UserId)
<input type="submit" />
}
And in your HttpPost
[HttpPost]
public ActionResult Edit(EditUserVM model)
{
var existingUser = GetUserFromYourDb(model.UserId);
existingUser.Name = model.Name;
existingUser.Email = model.Email;
SaveUser(existingUser);
return RedirectToAction("UserSaved");
}

Categories

Resources