I am very confused as to why this started happening as I had fixed the problem already. When this problem first occurred I did not have #Html.HiddenFor(model => model.OrganizationID) being passed through the POST action of the form. After I put that in - it worked just fine.
Now, it is not working again. The DbUpdateConcurrencyException is being thrown when I attempt to delete something. My Edit View works just fine.
I followed this tutorial to create a Model first approach.
These are the delete actions in my controller, OrganizationController:
public ActionResult Delete(int id)
{
using (var db = new VAGTCEntities())
{
return View(db.Organizations.Find(id));
}
}
//
// POST: /Organization/Delete/5
[HttpPost]
public ActionResult Delete(int id, Organization organization)
{
try
{
// TODO: Add delete logic here
using (var db = new VAGTCEntities())
{
db.Entry(organization).State = System.Data.EntityState.Deleted;
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
This is my delete view:
#model VAGTC.Models.Organization
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Organization</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.Name)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.Name)
</div>
</fieldset>
#using (Html.BeginForm()) {
#Html.HiddenFor(model => model.OrganizationID)
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
I debugged it to see if the id is being passed and it is indeed being passed through to the POST action. I am unsure of where to go from here. As anything I search for brings up just adding the HiddenFor statement.
This exception indicates that for some reason, no rows were affected by your query (ie. the row was not deleted). Instead of setting the State of the entry, try using the following approach:
db.Remove(organization);
db.SaveChanges();
Edited in response to comments
When you post back the organisation object, it will be populated by the fields contained in the form element of your view. In your case that is only OrganisationId. You change the State of the entity object posted to the controller and save it. If you have set the Concurrency Mode property on any of your EF entities (in the .edmx for example), EF will throw a concurrency exception if the value you pass for that field does not agree with the database value. That could be why it needs the value of the Name field.
A better approach (if you're not concerned about concurrency during deletes) would be to remove the object in the method signature, and rename the Id argument to recieve the OrganisationId. Then read the object directly from the database before setting its state and saving it.
Related
I try to pass some hidden data to my controller by using the hiddenFor, I know the value I want gets to the view, but after submiting the form the value stays null when it arrives in the controller. The data in EditorFor is passed correctly to the controller.
// View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// Some working editorFor fields. Data from these gets successfully received
// The name is correctly displayed in the paragraph
<p>#Model.name</p>
// This data is not received in the controller
#Html.HiddenFor(x => x.name)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
// Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product, HttpPostedFileBase image)
{
product.name = "a name";
return View(product);
}
I also tried using a normal named hidden, but this also didn't return a value.
Someone an idea what I missed?
You can pass the hidden fields automatically, if you have a form, using for example the razor helper
#using (Html.BeginForm("CreateTable", "Home", FormMethod.Post, null){ #HiddenFor(i => i.PropertyName) }
and the hidden fields must be inside of form, otherwise you will "lost" them.
Update following your updated question: Try remove the HiddenField and change <p>#Model.name</p>
to
#Html.LabelFor(i => i.Name)
I did focus on the incorrect thing, the problem was that I changed the model in the controller after the postback. But this only changes the model en does not changes the ModelState, which the form data uses.
//This is updated after model changes.
<p>#Model.name</p>
//For this you need to update the ModelState
#Html.HiddenFor(x => x.name)
In the controller you need to use ModelState.Remove(property name). (Or clear the complete ModelState)
//After removal of the property the ModelState will update to the new model value.
product.name = "a name";
ModelState.Remove("name");
return View(product);
In this article it's explained, https://weblog.west-wind.com/posts/2012/Apr/20/ASPNET-MVC-Postbacks-and-HtmlHelper-Controls-ignoring-Model-Changes.
I am using a generic repository to be the go between between presentation and EF. I am getting the following error when I try to update.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I know that this is generated because EF can't have 2 entities out with the same ID. So I need to either detach or transfer the values to the new object. I am choosing to transfer. However I can't figure out how to get the identity in a way that I don't have to pass it in every time.
public virtual void Update(T entity)
{
Context.Entry(Context.Set<T>().Find(entity.~theentityid~)).CurrentValues.SetValues(entity);
Context.Entry(entity).State = EntityState.Modified;
}
EDIT: The line Context.Entry(Context.Set().Find(entity.~theentityid~)).CurrentValues.SetValues(entity); was added after I started getting the error in an effort to solve it.
My identity columns are not all named Id so I can't use that. Is there a generic way to grab the identity value no matter which entity type is passed in so that I can find the entity that is already attached.
EDIT: Based on the comments I am including more code. My GalleryCollectionRepository inherits the generic repository that is listed above.
private static IntranetEntities db = new IntranetEntities();
private GalleryCollectionRepository GalleryCollectionRepo = new GalleryCollectionRepository(db);
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "collectionID,name,description,createdOn,createdByCommonName,createdByUsername,expirationDate,workOrderID")] GalleryCollection collection)
{
if (ModelState.IsValid)
{
GalleryCollectionRepo.Update(collection);
GalleryCollectionRepo.Save();
return RedirectToAction("Index");
}
return View(collection);
}
The edit form is the standard autogenerated form.
This is a sample of the top section of the form the rest of the form is all standard stuff so didn't want to make the post really long but including it.
#model Marketing.Models.GalleryCollection
#{
ViewBag.Title = "Edit";
Layout = "~/Areas/Intranet/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Collection</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.collectionID)
<div class="form-group">
#Html.LabelFor(model => model.collectionName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.collectionName)
#Html.ValidationMessageFor(model => model.collectionName)
</div>
</div>
I have the following [HttpGet] Create() method:
public ActionResult Create(int? parentId)
{
var model = new CreatePersonViewModel();
// pull parent from db
var parent = _db.Persons.FirstOrDefault(s => s.Id == parentId);
model.Parent = parentSet;
return View("Create", model);
}
If I'm creating a new Person from another person's Details page, I pass in the ID of that parent Person and then construct a viewModel with the Parent included.
The POST looks like this:
[HttpPost]
public ActionResult Create(CreatePersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var parent = viewModel.Parent; // This is always null for some reason
var person = new Person() { Name = viewModel.Name };
// if it has a parent, build new relationship
if (parent != null)
{
person.Parent = parent;
parent.Children.Add(person);
};
_db.Save();
return RedirectToAction("detail", "person", new { personId = person.Id });
}
return View(viewModel);
}
For some reason the viewModel getting pushed back to the POST method never contains the Parent that was defined in the GET controller method. How can I tell MVC to push the parent from GET to POST, without muddling the View with a hidden field for Parent?
In case it helps, my view is here:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreatePersonViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The controller action will only receive data that is explicitly included in the HTML form.
If you want to receive the Parent object, you'll need to put it in hidden <input> tags.
Note that any data that comes from the client is under complete control of your attacker and cannot be trusted.
The web is stateless so how would the server know what data you wanted to pass behind the scenes?
You could use TempData to pass data and it will remain on the server until it is read by your next request. However I don't believe you need that here. Since you aren't using anything but the parent id and the name, store that on the client - ie your viewmodel contains only those fields- no entity in your viewmodel.
When you post to the server load your parent, assign a new child and save it. No sense sending the entire object to the client.
Also I would verify on te server that the current user has access to those records - if applicable here unless your application allows all users access to all persons.
How can I pass back a (binded) model from a view to a controller method that is not an [httppost]?
View:
#Html.EditorFor(model => model.Name)
#Html.DisplayFor(model => model.Model)
// Table displays more properties of model
<input type="button" title="GetStuff" value="Get Stuff" onclick="location.href='#Url.Action("GetStuff")'" />
Controller Method:
public ActionResult GetStuff(ViewModel Model /* Realize this is non-httppost but...how can I get the model back with this type of scenario */)
{
// Get the name from per the model of the main view if the user changed it
string theName = Model.Name;
Model2 m2 = new Model2(){name = theName};
return ("someView", Model2);
}
Instead of writing your own query strings, simply wrap your EditorFor stuff in an using Html.BeginForm() statement, remove the Javascript from your button, and change the button to a submit type.
#using (Html.BeginForm("GetStuff", "Controller", FormMethod.Get)
{
#Html.EditorFor(model => model.Name)
#Html.DisplayFor(model => model.Model)
// Table displays more properties of model
<input type="submit" title="GetStuff" value="Get Stuff"/>
}
The FormMethod.Get will send a query string request of the form elements to the action/controller defined in the Html.BeginForm definition.
Since the way you pass data in GET requests is via the query string, you'd have to put all that data on the query string. Model binding may work, at least for simple models, but you'll have to explicity include all that data in the action. For example:
#Url.Action("GetStuff", new { Name = Model.Name, Model = Model.Model })
But if any of that data is complex, you'll have to specify each and every subproperty yourself... Perhaps you should just post? :)
The issue I am having is when i pass a populated object to a view that doesn't display all of the properties.
public ActionResult Edit(Guid clientID)
{
Property property = PropertyModel.GetPropertyDetails(clientID);
return View("Edit", "Site", property);
}
View:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<WebFrontend.ViewModels.Property>" %>
<% using (Html.BeginForm())
{%>
<%: Html.ValidationSummary(true, "Property update was unsuccessful. Please correct the errors and try again.") %>
<fieldset>
<legend>Edit Account: <%: Model.ClientAccountNo %></legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.ClientName)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.ClientName)%>
<%: Html.ValidationMessageFor(model => model.ClientName)%>
</div>
<p>
<input type="submit" value="Update Property" />
</p>
</fieldset>
<% } %>
When submitted the Property object is passed to this controller method but all of the properties not used in the view are null or empty including Model.ClientAccountNo which is present on the view before submitting.
[HttpPost]
public ActionResult Edit(Property property)
{
if (ModelState.IsValid)
{
bool success = PropertyModel.UpdateProperty(property);
if (success)
{
// property has been updated, take them to the property details screen.
return RedirectToAction("Details", "Property", new { clientID = property.ClientID });
}
else
{
ModelState.AddModelError("", "Could not update property.");
return View("Activate", "Site", property);
}
}
// If we got this far, something failed, redisplay form
return View("Edit", "Site", property);
}
I can't find anything similar on the web, any explanation for why this is happening and how to fix it would be appreciated.
This is the way MVC works - it tries to construct an object of the action parameter type from the values in the route, form and query string. In your case it can only get the values in the form collection. It does not know anything about the values that you have stored in your database.
If you are only interested in certain properties you would be better off doing a specific view model with just these on - then you can validate for this specific case.
If you store values in hidden fields keep in mind that these can be manipulated - if this is a problem definitely go with a specific view model with only the editable fields on.
Since you are not passing unused properties in URL, then you will have to render hidden fields for them. Use Html.HiddenFor method.