Search code examples
c#asp.net-mvcasp.net-corecrudasp.net-core-identity

I am getting HTTP ERROR 404 Exception in Edit Action


Here is the code for Edit:

[HttpGet]
public async Task<IActionResult> Edit(string id)
{
    var roles = _roleManager.Roles.ToList();
    ViewBag.Roles = new SelectList(roles, "Id", "Name");
    ApplicationUser user = await _userManager.FindByIdAsync(id);

    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {id} cannot be found";
        return NotFound();
    }

    var userRole = _userManager.GetRolesAsync(user).ToString();

    var userViewModel = new EditUserViewModel
    {
        UserId=user.Id,
       Username=user.UserName,
       FirstName=user.FirstName,
       LastName=user.LastName,
       Email=user.Email,
       UserPIN=user.UserPIN,
       Address=user.Address,
       TelephoneNumber=user.PhoneNumber,
        Roles = userRole
    };

    return View(userViewModel);
}

      
[HttpPost]
public async Task<IActionResult> Edit(EditUserViewModel userViewModel)
{
    var user = await _userManager.FindByIdAsync(userViewModel.UserId);

    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {userViewModel.UserId} cannot be found";
        return NotFound();
    }
    else
    {
        user.UserName = userViewModel.Username;
        user.FirstName = userViewModel.FirstName;
        user.LastName = userViewModel.LastName;
        user.Email = userViewModel.Email;
        user.UserPIN = userViewModel.UserPIN;
        user.Address = userViewModel.Address;
        user.PhoneNumber = userViewModel.TelephoneNumber;

        var result = await _userManager.UpdateAsync(user);
        _dbContext.SaveChanges();

        if (result.Succeeded)
        {
            return RedirectToAction("Index");
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError("", error.Description);
        }

        return View(userViewModel);
    }
}

This is my view model for Edit:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Models.UserViewModels
{
    public class EditUserViewModel
    {
        public string UserId { get; set; }

        [Required]
        [StringLength(50, MinimumLength = 6,
            ErrorMessage = "Username cannot be shorter than 6 characters and longer than 50.")]
        public string Username { get; set; }

        [Required]
        public string FirstName { get; set; }

        [Required]
        public string LastName { get; set; }

        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Required]
        [StringLength(10, MinimumLength = 10,
            ErrorMessage = "User PIN must be 10 characters long")]
        public string UserPIN { get; set; }

        [Required]
        [StringLength(10, MinimumLength = 10,
            ErrorMessage = "Telephone number must be 10 characters long")]
        public string TelephoneNumber { get; set; }

        [Required]
        public string Address { get; set; }

        [Required]
        public string Roles { get; set; }
    }
}

And this is my View:

@model Project.Models.UserViewModels.EditUserViewModel

<h1>Edit User</h1>

<div class="col-md-4">
    <form asp-action="Edit" asp-controller="Admin" method="post">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="FirstName"> First name</label>
            <input asp-for="FirstName" class="form-control" />
            <span asp-validation-for="FirstName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="LastName"> Last name</label>
            <input asp-for="LastName" class="form-control" />
            <span asp-validation-for="LastName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Username"> Username</label>
            <input asp-for="Username" class="form-control" />
            <span asp-validation-for="Username" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="UserPIN">User PIN</label>
            <input asp-for="UserPIN" class="form-control" />
            <span asp-validation-for="UserPIN" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Email"></label>
            <input asp-for="Email" class="form-control" />
            <span asp-validation-for="Email" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="TelephoneNumber"></label>
            <input asp-for="TelephoneNumber" class="form-control" />
            <span asp-validation-for="TelephoneNumber" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Roles">Role</label>
            <select asp-for="Roles" class="form-control" asp-items="@ViewBag.Roles"></select>
            <span asp-validation-for="Roles" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Address">Address</label>
            <input asp-for="Address" class="form-control" />
            <span asp-validation-for="Address" class="text-danger"></span>
        </div>
        <div class="form-group">
            <input type="submit" value="Save" class="btn btn-primary" />
        </div>
    </form>
</div>

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

I create an asp.net core application using Identity. Now I am customizing the project. I wrote AddUser and role and Delete Actions and they are working.
I get HTTP ERROR 404 exception when I try to edit my user in UserList.
I add _dbContext.SaveChanges(), but it is still not working. The rest CRUD operations are working properly like I said earlier.


Solution

  • The code that saves your model depends on the presence of the userViewModel.UserId information. But this is not present in the .cshtml code. So when the model is passed back to the controller in the HttpPost there is no data and the _userManager.FindByIdAsync will not find any user. The fix is simple, just add somewhere in the .cshtml an hidden field for the UserId

    <form asp-action="Edit" asp-controller="Admin" method="post">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div> <input asp-for="UserId" class="d-none" /></div>
        ....
    

    It is an hard fact to remember that the istance of the ViewModel passed to the view from the HttpGet action is not the same instance passed back to the HttpPost action. It is a new ViewModel instance and it is populated from the fields present in the View. If you forget a field then there is no value in the model passed back to the post action.