Example E5125
Visible to All Users

Grid View for ASP.NET MVC - How to implement optimistic concurrency for update and delete operations

This example illustrates how to implement optimistic concurrency when you bind the GridView extension to a data source using the Entity Framework and the "Code First" development approach. See the following topic for more information: Bind Grid View to Data via Entity Framework (Code First).

Note that Entity Framework includes the corresponding functionality out-of-the-box if you define the Timestamp field in your data model:

C#
[Timestamp] public Byte[] RowVersion { get; set; }

Once you define the Timestamp field, entity framework automatically performs concurrency checks when saving data to the database. In case of concurrency conflict, the DbUpdateConcurrencyException is thrown.

In stateless environments like ASP.NET MVC, you need some additional logic over the default Entity Framework's logic to keep the current RowVersion value for the specific object between postbacks. To pass RowVersion-related data to the client, you can use the approach illustrated in the following topic: Passing Values Between Server and Client Sides.

C#
settings.CustomJSProperties = (s, e) => { MVCxGridView gridView = (MVCxGridView)s; var dictionary = new System.Collections.Generic.Dictionary<object, string>(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/); for (int i = 0; i < gridView.SettingsPager.PageSize; i++) { var visibleIndex = i + gridView.VisibleStartIndex; if (visibleIndex >= gridView.VisibleRowCount) break; object[] rowValues = (object[])gridView.GetRowValues(visibleIndex, gridView.KeyFieldName, "RowVersion"); dictionary[rowValues[0].ToString(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/)] = Convert.ToBase64String((byte[])rowValues[1]); } e.Properties["cpRowVersions"] = new System.Web.Script.Serialization.JavaScriptSerializer(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/).Serialize(dictionary); if (ViewData["EditError"] != null) e.Properties["cpEditError"] = ViewData["EditError"]; };

After that, pass this data back to the corresponding Update or Delete controller's action method on the server side. Refer to the following topic for more information: Passing Values to a Controller Action through Callbacks.

JavaScript
function GridView_BeginCallback(s, e) { e.customArgs['RowVersions'] = s.cpRowVersions; }

On the server side, deserialize this data and pass the correct value to the RowVersion field of the currently updated or deleted object.

C#
public ActionResult GridViewPartialUpdate(Customer customer) { var model = db.Customers; customer.RowVersion = CalculateOldRowVersion(customer.Id); if (ModelState.IsValid) { try { db.Entry(customer).State = System.Data.Entity.EntityState.Modified; db.SaveChanges(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", db.Customers.ToList(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/)); } private byte[] CalculateOldRowVersion(int id) { JavaScriptSerializer serializer = new JavaScriptSerializer(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/); string rowVersions = Request["RowVersions"]; Dictionary<object, string> dictionary = (Dictionary<object, string>)serializer.Deserialize(rowVersions, typeof(Dictionary<object, string>)); char[] rowVersion = dictionary[id.ToString(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/)].ToCharArray(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/); return Convert.FromBase64CharArray(rowVersion, 0, rowVersion.Length); }

Files to Review

Documentation

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

Controllers/HomeController.cs(vb)
C#
using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using System.Web.Script.Serialization; using GridViewOptimisticConcurrencyMvc.Models; namespace GridViewOptimisticConcurrencyMvc.Controllers { public class HomeController : Controller { private CustomerDbContext db = new CustomerDbContext(); public ActionResult Index() { return View(); } public ActionResult GridViewPartial() { return PartialView(db.Customers.ToList()); } public ActionResult GridViewPartialAddNew(Customer customer) { var model = db.Customers; if (ModelState.IsValid) { try { db.Entry(customer).State = System.Data.Entity.EntityState.Added; db.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", db.Customers.ToList()); } public ActionResult GridViewPartialUpdate(Customer customer) { var model = db.Customers; customer.RowVersion = CalculateOldRowVersion(customer.Id); if (ModelState.IsValid) { try { db.Entry(customer).State = System.Data.Entity.EntityState.Modified; db.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", db.Customers.ToList()); } public ActionResult GridViewPartialDelete(Customer customer) { var model = db.Customers; customer.RowVersion = CalculateOldRowVersion(customer.Id); if (ModelState.IsValid) { try { db.Entry(customer).State = System.Data.Entity.EntityState.Deleted; db.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", db.Customers.ToList()); } private byte[] CalculateOldRowVersion(int id) { JavaScriptSerializer serializer = new JavaScriptSerializer(); string rowVersions = Request["RowVersions"]; Dictionary<object, string> dictionary = (Dictionary<object, string>)serializer.Deserialize(rowVersions, typeof(Dictionary<object, string>)); char[] rowVersion = dictionary[id.ToString()].ToCharArray(); return Convert.FromBase64CharArray(rowVersion, 0, rowVersion.Length); } } }
Models/Customer.cs(vb)
C#
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; namespace GridViewOptimisticConcurrencyMvc.Models { public class Customer { [Key] public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Email { get; set; } [Timestamp] public Byte[] RowVersion { get; set; } } public class CustomerDbContext : DbContext { public CustomerDbContext() : base("CustomerDbContext") { Database.SetInitializer(new CustomerDbContextInitializer()); } public DbSet<Customer> Customers { get; set; } } public class CustomerDbContextInitializer : DropCreateDatabaseIfModelChanges<CustomerDbContext> { protected override void Seed(CustomerDbContext context) { IList<Customer> defaultCustomers = new List<Customer>(); defaultCustomers.Add(new Customer() { FirstName = "David", LastName = "Adler", Age = 25, Email = "David.Adler@somewhere.com" }); defaultCustomers.Add(new Customer() { FirstName = "Michael", LastName = "Alcamo", Age = 38, Email = "Michael.Alcamo@somewhere.com" }); defaultCustomers.Add(new Customer() { FirstName = "Amy", LastName = "Altmann", Age = 27, Email = "Amy.Altmann@somewhere.com" }); foreach (Customer std in defaultCustomers) context.Customers.Add(std); base.Seed(context); } } }
Views/Home/GridViewPartial.cshtml
Razor
@model System.Collections.IEnumerable @Html.DevExpress().GridView(settings => { settings.Name = "GridView"; settings.KeyFieldName = "Id"; settings.CallbackRouteValues = new { Controller = "Home", Action = "GridViewPartial" }; settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Home", Action = "GridViewPartialAddNew" }; settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Home", Action = "GridViewPartialUpdate" }; settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "GridViewPartialDelete" }; settings.CommandColumn.Visible = true; settings.CommandColumn.ShowEditButton = true; settings.CommandColumn.ShowNewButton = true; settings.CommandColumn.ShowDeleteButton = true; settings.Columns.Add(column => { column.FieldName = "Id"; column.ReadOnly = true; column.EditFormSettings.Visible = DefaultBoolean.False; }); settings.Columns.Add("FirstName"); settings.Columns.Add("LastName"); settings.Columns.Add("Age"); settings.Columns.Add("Email"); settings.CustomJSProperties = (s, e) => { MVCxGridView gridView = (MVCxGridView)s; var dictionary = new System.Collections.Generic.Dictionary<object, string>(); for (int i = 0; i < gridView.SettingsPager.PageSize; i++) { var visibleIndex = i + gridView.VisibleStartIndex; if (visibleIndex >= gridView.VisibleRowCount) break; object[] rowValues = (object[])gridView.GetRowValues(visibleIndex, gridView.KeyFieldName, "RowVersion"); dictionary[rowValues[0].ToString()] = Convert.ToBase64String((byte[])rowValues[1]); } e.Properties["cpRowVersions"] = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(dictionary); if (ViewData["EditError"] != null) e.Properties["cpEditError"] = ViewData["EditError"]; }; settings.ClientSideEvents.BeginCallback = "GridView_BeginCallback"; settings.ClientSideEvents.EndCallback = "GridView_EndCallback"; }).SetEditErrorText((string)ViewData["EditError"]).Bind(Model).GetHtml()
Views/Home/Index.cshtml
Razor
<script type="text/javascript"> function GridView_BeginCallback(s, e) { e.customArgs['RowVersions'] = s.cpRowVersions; } function GridView_EndCallback(s, e) { if (s.cpEditError) { alert('Error: ' + s.cpEditError); delete s.cpEditError; } } </script> @Html.Action("GridViewPartial")

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.