2

I have created an Admin section of my MVC5/EF6 application. I can successfully Create() and Delete() Users, but my Edit() continues to error out on the POST.

Edit View

@model PROJECT.Models.ApplicationUser

@{
    ViewBag.Title = "Edit";
    Layout = "~/Areas/Admin/Views/Shared/_LayoutAdmin.cshtml";
    PROJECT.Helper.GeneralHelper helper = new PROJECT.Helper.GeneralHelper();
    string cancelEditUrl = "/Admin/UserManagement/";
}

@using (Html.BeginForm("Edit", "UserManagement", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    @Html.HiddenFor(model => model.Id)
    @Html.HiddenFor(model => model.ForumUsername)
    @Html.HiddenFor(model => model.ReceiveSystemEmails)
    @Html.HiddenFor(model => model.RegisteredDate)
    @Html.HiddenFor(model => model.LastVisitDate)

    <div class="container">
        <div class="row">
            <div class="editor-label">
                @Html.LabelFor(model => model.Name)
            </div>

            <div class="editor-field">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>

        <div class="row">
            <div class="editor-label">
                @Html.LabelFor(model => model.Position)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.Position, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Position)
            </div>
        </div>

        <div class="row">
            <div class="editor-label">
                @Html.LabelFor(model => model.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.Email, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>

        <div class="row">
            <div class="editor-label">
                @Html.LabelFor(model => model.PhoneNumber)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.PhoneNumber, new { @class = "form-control", type = "tel" })
                @Html.ValidationMessageFor(model => model.PhoneNumber)
            </div>
        </div>

        <div class="row">
            <div class="col-md-1" style="padding-left: 0px">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
            <div class="col-md-9" style="padding-left: 0px">
                <a href="@cancelEditUrl" class="btn btn-danger">Cancel</a>
            </div>

            @{
                string deleteURL = "/Admin/UserManagement/Delete/" + Model.Id;
            }
            <a href=@deleteURL class="btn btn-danger col-md-2">
                <span class="glyphicon glyphicon-minus"></span> Delete User
            </a>
        </div>
  </div>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Controller:

    // GET: Admin/UserManagement/Edit/5
    public async Task<ActionResult> Edit(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ApplicationUser model = await UserManager.FindByIdAsync(id);
        if (model == null)
        {
            return HttpNotFound();
        }
        return View(model);
    }

    // POST: Admin/UserManagement/Edit/5
    // 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 async Task<ActionResult> Edit([Bind(Include = "Id,property1, property2, etc...")] ApplicationUser applicationUser)
    {
        if (ModelState.IsValid)
        {
            db.Entry(applicationUser).State = EntityState.Modified;
            await db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(applicationUser);
    }

When I create a user, all I have to enter is a Name & Email on the GUI side, such as Test User and testuser@project.org. When the user is created, I am returned to my Index view, where I can choose to Edit my new User. If I open my Edit View and then add a position of say Test Monkey and click save, my Controller's POST Edit() code executes, but at await db.SaveChangesAsync(); immediately jumps down to my DB dispose():

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }

and I continue from there I get the following Error Details:

Server Error in '/' Application.

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

Anyone have ideas on what may be going on? This was previously a functioning section of the project developed under MVC4/EF5 and then it was upgraded to make use of enhancements in MVC5 such as Identity.

EDIT:

Implemented an Application Trace as suggested by David Martin:

Validation failed for one or more entities while saving changes to SQL Server Database using Entity Framework

View ASP.Net Trace Information Application Trace

Not quite sure what it is I am now looking for?

EDIT2:

Still not sure what I'm after exactly on the Trace, but using my breakpoint in the Catch I drilled down into [dbEx] and found the true error message was "The UserName field is required".

dbException

By adding applicationUser.UserName = applicaitonUser.Email (which is the same thing in my current application) I am able to Edit and Save changes to my Users now.

What I can't seem to figure out however, is why this is required in the first place. As can be seen below (and I have confirmed), the User's [UserName] value is set within my POST Create() method:

    // POST: Admin/UserManagement/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 async Task<ActionResult> Create([Bind(Include = "Id,property1,property2,etc.,UserName")] ApplicationUser applicationUser)
    {
        if ((applicationUser.MemberOrgId == null) && (applicationUser.SponsorOrgId == null))
        {
            ModelState.AddModelError("", "Must select either an Organization or Sponsor from the dropdown lists.");
            return View(applicationUser);
        }

        if (ModelState.IsValid)
        {
            ViewBag.headerTitle = "Create User";
            applicationUser.UserName = applicationUser.Email;
            IdentityResult result = await UserManager.CreateAsync(applicationUser, "test12345");
            if (result.Succeeded) 
            {
                await db.SaveChangesAsync();
                return RedirectToAction("Index", "UserManagement");
            }
            else
            {
                ModelState.AddModelError("", "Failed to Create User.");
                var errors = string.Join(",", result.Errors);
                ModelState.AddModelError("", errors);
            }
        }

        ModelState.AddModelError("", "Failed to Create User.");
        var errors1 = ModelState.Where(x => x.Value.Errors.Count > 0).Select(x => new { x.Key, x.Value.Errors }).ToString();
        ModelState.AddModelError("", errors1);

        ViewData["Organization"] = new SelectList(db.MemberOrganizations, "Id", "Name", applicationUser.MemberOrgId);
        ViewData["Sponsor"] = new SelectList(db.SponsorOrganizations, "Id", "Name", applicationUser.SponsorOrgId);
        if (applicationUser.MemberOrgId != null)
        {
            ViewBag.SwitchState = true;
        }
        else
        {
            ViewBag.SwitchState = false;
        }
        ViewBag.OrganizationId = new SelectList(db.MemberOrganizations, "Id", "State", applicationUser.MemberOrgId);

        // If we got this far, something failed, redisplay form
        return View(applicationUser);   
    }

Thus, it should not be flagged as required in my POST Edit() as it has already been set?

Community
  • 1
  • 1
Analytic Lunatic
  • 3,853
  • 22
  • 78
  • 120
  • Can you run in debug and see if you can catch the exception being thrown? – Pseudonym Jun 10 '14 at 13:29
  • 2
    Have you checked the EntityValidationErrors property? The answer will lie there. – ozz Jun 10 '14 at 13:40
  • @PseudoNym01 I added a try-catch to my Edit POST method: `return Json(new { error = ex.Message + ",\n" + ex.InnerException + ",\n" + ex.Data + ",\n" + ex.Source });` This returns: `{"error":"Validation failed for one or more entities. See \u0027EntityValidationErrors\u0027 property for more details.,\n,\nSystem.Collections.ListDictionaryInternal,\nEntityFramework"}`. Does that help? @Ozz I've been attempting to figure out how to check that specific property, but no luck yet. – Analytic Lunatic Jun 10 '14 at 13:46
  • 1
    See here for EntityValidationErrors - http://stackoverflow.com/questions/5400530/validation-failed-for-one-or-more-entities-while-saving-changes-to-sql-server-da – David Martin Jun 10 '14 at 14:37
  • @DavidMartin, Thanks! I was looking around but could never find the namespace to use :) This is probably a dumb question, but where/how do I view the `Trace.TraceInformation()` that is gathered? – Analytic Lunatic Jun 10 '14 at 14:57
  • @DavidMartin, as shown in my EDIT above I have implemented the Application Trace you suggested, but I am not quite sure what to do with this information or how to use it to view my `EntityValidationsErrors` property? – Analytic Lunatic Jun 10 '14 at 15:24
  • Thanks everyone for the assistance! I figured out my way around the issue, though if you view my latest Edit I'm not sure why [UserName] is being required when I have already set (and confirmed) the value during User Create(). – Analytic Lunatic Jun 10 '14 at 16:56

1 Answers1

0

Not sure if i am mistaking, but it seems like you aren't preserving the UserName in your View to be modelbound (eg. by using @Html.HiddenFor(x=>x.UserName). Furthermore, it seems like the UserName in applicationUser.UserName = applicationUser.Email;is set after you have checked if (ModelState.IsValid) in your POST Action?