Why would Entity Framework carry out update when RowVersion has changed? - asp.net-mvc

I'm having a problem trying to catch a DbUpdateConcurrencyException using Entity Framework 5. The problem that I'm having is that EF is updating the record, even though the RowVersion (Timestamp) property has changed since the row was retrieved from the database. The HttpGet Edit action gets a user profile from the database and I pass the values to a ViewModel, including a list of checkboxes for the user to select the roles and pass this to the view.
public ActionResult Edit(int id = 0)
{
UserProfile userProfile = unitOfWork.UserProfileRepository.GetUserProfileById(id);
UserProfileEditViewModel viewModel = new UserProfileEditViewModel
{
UserId = userProfile.UserId,
UserName = userProfile.UserName,
FirstName = userProfile.FirstName,
LastName = userProfile.LastName,
Email = userProfile.Email,
RowVersion = userProfile.RowVersion,
};
var allRoles = unitOfWork.RoleRepository.GetAllRoles();
var userProfileRoles = userProfile.Roles;
foreach (var role in allRoles)
{
if (userProfileRoles.Contains(role))
{
viewModel.Roles.Add(new RoleViewModel
{
RoleId = role.RoleId,
RoleName = role.RoleName,
Assigned = true,
});
}
else
{
viewModel.Roles.Add(new RoleViewModel
{
RoleId = role.RoleId,
RoleName = role.RoleName,
Assigned = false,
});
}
}
return View(viewModel);
}
I then have a basic edit view that has a HiddenFor for the RowVersion property.
#model MvcWebsite.ViewModels.UserProfileEditViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UserProfile</legend>
#Html.HiddenFor(model => model.UserId)
#Html.HiddenFor(model => model.UserName)
#Html.HiddenFor(model => model.RowVersion)
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-field">
<table>
<tr>
#Html.EditorFor(model => model.Roles)
#Html.ValidationMessageFor(model => model.Roles)
</tr>
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I then have a HttpPost Edit action that takes the data from the viewModel and adds it to a user profile that I've retrieved from the database. I then change the properties of this profile to those that were retrieved from the client, including the RowVersion (changing the RowVersion back to its original state)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserProfileEditViewModel model)
{
try
{
if (ModelState.IsValid)
{
var userProfile = unitOfWork.UserProfileRepository.GetUserProfileById(model.UserId);
userProfile.UserName = model.UserName;
userProfile.FirstName = model.FirstName;
userProfile.LastName = model.LastName;
userProfile.Email = model.Email;
userProfile.RowVersion = model.RowVersion;
var roleAssignments = model.Roles;
foreach (var roleAssignment in roleAssignments)
{
if (roleAssignment.Assigned)
{
userProfile.Roles.Add(unitOfWork.RoleRepository.GetRoleById(roleAssignment.RoleId));
}
else
{
userProfile.Roles.Remove(unitOfWork.RoleRepository.GetRoleById(roleAssignment.RoleId));
}
}
unitOfWork.UserProfileRepository.UpdateUserProfile(userProfile);
unitOfWork.Save();
return RedirectToAction("Details", new { id = userProfile.UserId });
}
}
catch (DbUpdateConcurrencyException ex)
{
... Code omitted for brevity
}
}
return View(model);
}
I test this by opening the edit page twice. I then update the second page and click save, which commits the changes to the database. The database shows that the row version has in fact been changed to reflect the update. When I change the second page and click save, even though the row version of this profile is different from the row version that was created when the first profile was saved, the changes are saved to the database also. I've checked that indeed, the row version in the database is actually changed twice.
I have a funny feeling that I'm missing the obvious here, but any help would be greatly appreciated.

typically you don't change the row version explicitly. this would be handled by the ORM itself. What you would do is compare the view model version to the domain version. if they do not match you then need to handle that scenario.
if(viewmodel.RowVersion != domainModel.RowVersion)
{
//model has changed, notify user...
}
else
{
//update domain model
//save changes
}
You could further prevent this by making RowVersion immutable
class domainmodel
{
...
public int RowVersion {get; private set;}
}

Related

Passing data from a View to a Controller in .NET MVC - "#model" not highlighting

The following code works as I need it to:
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</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>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
In LOLOLController:
[HttpPost]
public IActionResult LOLOL(Patient p) {
var client = new MongoClient("mongodb://localhost:27017");
var userId = _userManager.GetUserId(HttpContext.User);
string db_name = "test" + userId;
var database = client.GetDatabase(db_name);
var collection = database.GetCollection<BsonDocument>("patients");
var filter = Builders<BsonDocument>.Filter.Eq("Name", p.Name.ToString());
var document = collection.Find(filter).First();
// I'm cutting short the rest of the code, because when I do something
// similar later, "collection.Find(filter).First()" fires an exception, I'll
// explain..
return View(p);
}
I have something equivalent to taking off the fieldset element in the HTML, leaving basically just a button in the "Html.BeginForm", but then the data is clearly not binding properly, which I know because if I just have a button and no data-entry, I click the button and then I get an error saying the data cannot be found from the database. (EDIT: I now have confirmed that this is indeed because the Patient object is not being passed to the controller quite as I expected it to, seems like a brand new Patient object was created upon calling html.beginform ... I thought that maybe the old Patient object was being passed so I did not have to enter all its data members every time we use Html.BeginForm)
In sum, I want to fill out a text box, click a button to load a new page and display the value of that textbox, but have that value also persisted in essentially a session state, so that if I call another Html.BeginForm function and go into a third view, the text from the first view will be displayed in the third view, even though I did not have to type its value in the second view. Hopefully I can repeat this process, and essentially load up the data members of a class with one view per data member.
Make sure you pass the data from the previous view to the new view from your Controller. When you pass it, include #HiddenFor for those properties from the previous view in your new view. That way the new view will keep and then pass the values to your next POST.
#Html.HiddenFor(model => model.PropertyYouPassedAndWantToKeepAndPassAgain
Edit: Here's the logic for using multiple views for one object... as requested.
Model:
public class Patient
{
string Name { get; set; }
string Address { get; set; }
string City { get; set; }
}
Page1 GET:
[HttpGet]
public ActionResult Page1()
{
Patient patient = new Patient();
return View("~/Views/Page1.cshtml", patient);
}
Page 1 View... only ask for the name.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</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>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Page1 POST... get the patient and pass it on to the next view...
[HttpPost]
public ActionResult Page1(Patient patient)
{
if (ModelState.IsValid)
{
return View("~/Views/Page2.cshtml", patient); // pass your patient to the second page view with the name
}
else
{
return View("~/Views/Page1.cshtml", patient);
}
}
Page2 GET... get the patient from the prior Page1 POST and send it off to the Page2 View.
[HttpGet]
public ActionResult Page2(Patient patient)
{
// Receive patient from Page1 post and pass it to new view... includes the name
return View("~/Views/Page2.cshtml", patient);
}
Page2 View gets the object... use a HiddenFor to keep the name which you just sent from the GET.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* This will keep the name on your next post *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Address)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address)
#Html.ValidationMessageFor(model => model.Address)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Since the HiddenFor holds the Name, it will be passed on your next post. It is there but hidden from the form itself.
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page2 POST
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page3 GET
[HttpGet]
public ActionResult Page3(Patient patient)
{
// Pass patient again... to your next view
return View("~/Views/Page3.cshtml", patient);
}
Page3 View...
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* Keep name again for your next post *#
#Html.HiddenFor(model => model.Address) #* Now we are keeping the address as well *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
And so on and so forth... until you have your model complete and want to do something with it.

MVC ViewModel collections are null on postback

I have a view model which contains a class reference and 2 IEnumerable collections.
public class BuildingTypeViewModel
{
public Static_Item BuildingType { get; set; }
public IEnumerable<Reading_Type> ReadingTypes { get; set; }
public IEnumerable<Building_Type_Reading> BuildingReadings { get; set; }
}
I populate this ViewModel in the Edit action on the controller
public ActionResult Edit(int id)
{
Static_Item staticItem = db.Static_Item.Find(id);
BuildingTypeViewModel model = new BuildingTypeViewModel
{
BuildingType = staticItem,
ReadingTypes=db.Reading_Type.ToList(),
BuildingReadings = db.Building_Type_Reading.Where(bt => bt.UN_Building_Type == staticItem.UN_Building_Type).ToList()
};
return View(model);
}
Building type on the view model is a class with an ID and Description and the data would be something like this:
UN_Building_Type=1, Description = "Hospital"
The IEnumerable of Reading_Type's would be like this:
UN_Reading_Type = 1, Description = "Electric"
UN_Reading_Type = 2, Description = "Gas"
The IEnumerable of Building_Type_Readings would be like this:
UN_Building_Type_Readings=1, UN_Building_Type=1, UN_Reading_Type = 1, Typical=300, Good=150
UN_Building_Type_Readings=2, UN_Building_Type=1, UN_Reading_Type = 2, Typical=800, Good=400
I load this data into my view:
#model SSE.Enterprise.EE_Web_Portal.Models.BuildingTypeViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Static_Item</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.BuildingType.UN_Building_Type)
<div class="form-group">
#Html.LabelFor(model => model.BuildingType.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BuildingType.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BuildingType.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Building Readings</label>
<div class="col-md-10">
<table class="table">
<tr>
<th/>
<th>
#Html.LabelFor(model=>model.BuildingReadings.FirstOrDefault().Typical)
</th>
<th>
#Html.LabelFor(model => model.BuildingReadings.FirstOrDefault().Good)
</th>
</tr>
#foreach (var item in Model.ReadingTypes)
{
<tr>
<td>
#Html.Label(item.Description)
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Typical, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Good, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</table>
</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>
Here is my edit postback method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "BuildingType")] BuildingTypeViewModel model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
When the page is posted back the 2 IEnumerable collections are null so I'm not able to save the data that has been entered.
Any idea?
Ta
First of all, by specifying the attribute Bind(Include = "BuildingType") you're telling MVC to only bind this single property. Remove the attribute, and MVC will try to bind your 2 IEnumerable collections.
Next, check your #Html.EditorFor calls. I'm not sure MVC can understand FirstOrDefault inside. Try avoiding LINQ selectors inside your view.
And after that, as #will mentioned, try changing IEnumerable to List.

ASP.NET MVC bool value returned from the view is 1 or 0

I am using ASP.NET MVC, and I have some problems using a CheckBoxFor. Here is my problem:
I have the following code in the view:
#Html.CheckBoxFor(model => model.stade, new { #id = "stade" })
model.stade is of type bool. In my controller, I have:
//Editar
[HttpPost]
public ActionResult InvoiceType(int Id, string Name, string Code, string Stade)
{
clsInvoiceTypea Model = new clsInvoiceType();
Model.Id = Id;
Model.Name = Name;
Model.Code = Code;
Model.Stade = stade== "1" ? true : false;
return PartialView(Model);
}
I get an error, because when Model.Stade is submitted to the view the value is 1 or 0, and I get an error saying "Can not recognize the string as a valid Boolean", but if Model.stade is boolean why the model is submitted to the view like 0 or 1? How I can I resolve this?
Here goes my solution -
Let your Model be -
public class clsInvoiceTypea
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public bool stade { get; set; }
}
Let your HttpGet Action be -
public ActionResult GetInvoice()
{
clsInvoiceTypea type = new clsInvoiceTypea();
return View(type);
}
And the corresponding view -
#model YourValidNameSpace.clsInvoiceTypea
#{
ViewBag.Title = "GetInvoice";
}
<h2>GetInvoice</h2>
#using (Html.BeginForm("SubmitData","Home",FormMethod.Post)) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>clsInvoiceTypea</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>
<div class="editor-label">
#Html.LabelFor(model => model.Code)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Code)
#Html.ValidationMessageFor(model => model.Code)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.stade)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.stade)
#Html.ValidationMessageFor(model => model.stade)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Let the following be your HttpPost Action -
[HttpPost]
public ActionResult SubmitData(clsInvoiceTypea model)
{
return View();
}
When you run the code, you will get following view -
When you select the checkbox and hit the create button, if you put a breakpoint on POST method and check the value, you will get true.

Hidden ViewModel Property defaulting to 0 when null - should stay null

Get Controller Method:
public ActionResult Create(int? parentId)
{
var model = new CreatePersonViewModel();
// pull set from db
var parent = _db.Persons.FirstOrDefault(s => s.PersonId == parentId);
if (parent != null)
{
model.ParentId = parent.PersonId;
}
return View("Create", model);
}
POST:
public ActionResult Create(CreatePersonViewModel viewModel) // viewModel.ParentId is 0 when it's passed to this method from the view, even though I didn't set it, and it's null coming from GET.
{
//bla bla
}
My View:
#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>
#Html.HiddenFor(model => model.ParentId) // tried with second parameter of "null" as well
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Any idea why the view is sending back a value of 0 in that property for cases when it should be null? I don't have any details to add here but the form validation is asking for more text so here it is.

Saving forms in mvc

I'm completely new to MVC having made the decision to try make the switch from aspx WebForms.
I have created a view using the MVC view creator wizard and selected a strongly typed class and an edit Scaffold template.
I got the following
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Company</legend>
#Html.HiddenFor(model => model.Id)
<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>
<div class="editor-label">
#Html.LabelFor(model => model.PhoneNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumber)
#Html.ValidationMessageFor(model => model.PhoneNumber)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
In the Controller I have the following to get the model details
[System.Web.Http.HttpGet]
public ActionResult Edit(int id)
{
var uow = new BlogUow();
var company = uow.Companies.GetById(id);
return View(company);
}
This works fine but the problem is, I'm stumped on how I actually save the newly entered details.
I relied too much on how the web forms handle post backs.
You must write a post action for your edit in the controller:
[System.Web.Http.HttpPost]
public ActionResult Edit(Companies company)
{
var uow = new BlogUow();
if (ModelState.IsValid)
{
uow.Entry(company).State = EntityState.Modified;
return RedirectToAction("Index");
}
return View(company);
}
note Companies overload
[HttpPost]
public ActionResult Edit(Companies company)
{
var uow = new BlogUow();
if (ModelState.IsValid)
{
Edit here
}
return View(company);
}

Resources