简体   繁体   中英

ASP.NET Core MVC ConcurrencyCheck is failing when the model property has the [ConcurrencyCheck] attribute

I'm new to concurrency checking in ASP.NET Core MVC and must be implementing something incorrectly here, however Microsoft documentation seems to be thin on the ground. Is anyone able to point me in the right direction?

This is a simple user class, which can be edited by an admin. Without any concurrency checking, the form changes are saved to the database with no issue, however, when I add the [ConcurrencyCheck] attribute to one of the model properties, the save to the database fails even when the field value is changed (I was expecting this to only fail when User A updated the field before User B did, etc.)

Any help would be greatly appreciated.

Here are some snippets.

Model

public class MyUser : IdentityUser
    {
        [ConcurrencyCheck]
        [PersonalData]
        [DisplayName("First Name")]
        [Column(TypeName = "nvarchar(100)")]
        public string FirstName { get; set; }

        [PersonalData]
        [DisplayName("Last Name")]
        [Column(TypeName = "nvarchar(100)")]
        public string LastName { get; set; }


        [DisplayName("Created Date")]
        [Column(TypeName = "DateTime")]
        public DateTime CreatedDateTime { get; set; }

        [DisplayName("Customer ID")]
        [Column(TypeName = "int")]
        public int CustomerId { get; set; }


        public string GetFullName()
        {
            return $"{FirstName} {LastName}";
        }

    }

Controller


 // GET: UserController/Edit/5
        public ActionResult Edit(string id)
        {
            // New user view model.
            UserViewModel userViewModel = new UserViewModel();

            // Get user details.
            MyUser user = _context.Users.Where(u => u.Id == id).Single();


           var roles = _context.UserRoles.Where(r => r.UserId == id).ToList();

            if (user != null)
            {
                // Set viewmodel user with user details.
                userViewModel.thisUser = user;

               // userViewModel.thisUserRoles = _context.UserRoles.Where()

                // Get roles assigned to the user.
                //userViewModel.thisUserRoles = _context.userRoles.Where(r => r.UserId == id).ToList();

                // Set viewmodel variables based on which roles the user has.
                if (roles.Count > 0)
                {
                    foreach(var role in roles)
                    {
                        switch (_context.Roles.Where(r => r.Id == role.RoleId).Select(r => r.Name).Single().ToString())
                        {
                            case "User":
                                userViewModel.UserRole = true;
                                break;
                            case "Sales User":
                                userViewModel.SalesUserRole = true;
                                break;
                            case "Sales Branch User":
                                userViewModel.SalesBranchUserRole = true;
                                break;
                            case "Sales Administrator":
                                userViewModel.SalesAdministrator = true;
                                break;
                            case "Administrator":
                                userViewModel.Administrator = true;
                                break;
                                    
                        }
                    }
                }

                return View(userViewModel);
            }
            else
            {
                ViewBag.ErrorMessage = "The user could not be found.";
                return RedirectToAction("Index");
            }
            
        }






 // POST: UserController/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(UserViewModel userViewModel)
        {

            if (ModelState.IsValid)
            {
                userViewModel.thisUser.UserName = userViewModel.thisUser.Email;
                userViewModel.thisUser.NormalizedUserName = userViewModel.thisUser.Email.ToUpper();
                userViewModel.thisUser.NormalizedEmail = userViewModel.thisUser.Email.ToUpper();


                try
                {

                    _context.Entry(userViewModel.thisUser).State = EntityState.Modified;
                    _context.SaveChanges();

                }
                catch (Exception ex)
                {
                    // Prompt the user to try again.
                    ModelState.AddModelError("", "The information has been updated by someone else before you saved. Please try again.");
                    return View(userViewModel);

                }





            }

            return RedirectToAction("Index");

        }

View

@model MyApp.ViewModels.UserViewModel

@{
    ViewData["Title"] = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@*<h1>Edit</h1>*@



@*<h5>
    Details
</h5>
<hr />*@

<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="All" class="text-danger"></div>


            @Html.HiddenFor(m => m.thisUser.Id)
            @Html.HiddenFor(m => m.thisUser.PasswordHash)
            @Html.HiddenFor(m => m.thisUser.LockoutEnd)
            @Html.HiddenFor(m => m.thisUser.LockoutEnabled)
            @Html.HiddenFor(m => m.thisUser.AccessFailedCount)
            @Html.HiddenFor(m => m.thisUser.CreatedDateTime)
            @Html.HiddenFor(m => m.thisUser.SecurityStamp)
            @Html.HiddenFor(m => m.thisUser.ConcurrencyStamp)
            @Html.HiddenFor(m => m.thisUser.EmailConfirmed)
            <div class="form-group">
                <label asp-for="thisUser.FirstName" class="control-label"></label>
                <input asp-for="thisUser.FirstName" class="form-control" />
                <span asp-validation-for="thisUser.FirstName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="thisUser.LastName" class="control-label"></label>
                <input asp-for="thisUser.LastName" class="form-control" />
                <span asp-validation-for="thisUser.LastName" class="text-danger"></span>
            </div>




            <div class="form-group">
                <label asp-for="thisUser.Email" class="control-label"></label>
                <input asp-for="thisUser.Email" class="form-control" />
                <span asp-validation-for="thisUser.Email" class="text-danger"></span>
            </div>
           
            <div class="form-group">
                <label asp-for="thisUser.PhoneNumber" class="control-label"></label>
                <input asp-for="thisUser.PhoneNumber" class="form-control" />
                <span asp-validation-for="thisUser.PhoneNumber" class="text-danger"></span>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="thisUser.PhoneNumberConfirmed" /> @Html.DisplayNameFor(model => model.thisUser.PhoneNumberConfirmed)
                </label>
            </div>
            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="thisUser.TwoFactorEnabled" /> @Html.DisplayNameFor(model => model.thisUser.TwoFactorEnabled)
                </label>
            </div>

            <div class="form-group">
                <label asp-for="thisUser.CustomerId" class="control-label"></label>
                <input asp-for="thisUser.CustomerId" class="form-control" />
                <span asp-validation-for="thisUser.CustomerId" class="text-danger"></span>
            </div>



            <h5>
                Roles
            </h5>
            <hr />

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="UserRole" /> @Html.DisplayNameFor(model => model.UserRole)
                </label>
            </div>

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="SalesUserRole" /> @Html.DisplayNameFor(model => model.SalesUserRole)
                </label>
            </div>

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="SalesBranchUserRole" /> @Html.DisplayNameFor(model => model.SalesBranchUserRole)
                </label>
            </div>

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="SalesAdministrator" /> @Html.DisplayNameFor(model => model.SalesAdministrator)
                </label>
            </div>

            <div class="form-group form-check">
                <label class="form-check-label">
                    <input class="form-check-input" asp-for="Administrator" /> @Html.DisplayNameFor(model => model.Administrator)
                </label>
            </div>



            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

According to ms official document, it recommends to use row version to handle concurrency conflicts .

To sum up from the document, we need to add a column named such as "RowVersion" which data type is "rowversion" and add it in your Entity.

    [Timestamp]
    public byte[] RowVersion { get; set; }

在此处输入图片说明

Next, before calling await _context.SaveChangesAsync(); we need to put that original RowVersion property value in the OriginalValues collection for the entity

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditAsync(UserViewModel userViewModel)
{
     if (ModelState.IsValid)
     {
         try
         {
               _context.Entry(userViewModel.thisUser).Property("RowVersion").OriginalValue = userViewModel.thisUser.RowVersion;
               _context.Update(userViewModel.thisUser);
               await _context.SaveChangesAsync();
          }
          catch (Exception ex)
          {
                    // Prompt the user to try again.
              ModelState.AddModelError("", "The information has been updated by someone else before you saved. Please try again.");
              return View(userViewModel);

          }
     }

     return RedirectToAction("Index");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM