36

I'm following this article to delete a user in Identity 2.0 http://www.asp.net/mvc/tutorials/mvc-5/introduction/examining-the-details-and-delete-methods

However, I need to delete all related records in AspNetUserRoles first and then delete the user.

I found an example which is written in Identity 1.0 and some of methods used inside this example don't exist.

   // POST: /Users/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> DeleteConfirmed(string id)
        {
            if (ModelState.IsValid)
            {
                if (id == null)
                {
                    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }

                var user = await context.Users.FindAsync(id);
                var logins = user.Logins;
                foreach (var login in logins)
                {
                    context.UserLogins.Remove(login);
                }
                var rolesForUser = await IdentityManager.Roles.GetRolesForUserAsync(id, CancellationToken.None);
                if (rolesForUser.Count() > 0)
                {

                    foreach (var item in rolesForUser)
                    {
                        var result = await IdentityManager.Roles.RemoveUserFromRoleAsync(user.Id, item.Id, CancellationToken.None);
                    }
                }
                context.Users.Remove(user);
                await context.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            else
            {
                return View();
            }
        }

I cannot find IdentityManager from anywhere, and context.Users doesn't have FindAsync() method either.

How can I properly delete a User and its related records in Identity 2.0?

halfer
  • 19,824
  • 17
  • 99
  • 186
Franva
  • 6,565
  • 23
  • 79
  • 144

4 Answers4

70

I think the classes you're looking for are the UserManager and the RoleManager. In my opinion they are the better way instead of going against the context directly.

The UserManager defines a method RemoveFromRoleAsync which gives you the ability to remove the user (identified by his key) from a given role. It also defines several Find methods, such as FindAsync, FindByIdAsync, FindByNameAsync, or FindByEmailAsync. They all can be used to retrieve a user. To delete a user you should use the DeleteAsync method which accepts a user object as a parameter. To get the roles a user is member of Identity gives you the GetRolesAsync method where you pass in the ID of the user. Also I see that you're trying to remove a login from a user. For this purpose you should use the RemoveLoginAsync method.

All in all your code would look similar to the following one:

// POST: /Users/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(string id)
{
  if (ModelState.IsValid)
  {
    if (id == null)
    {
      return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    var user = await _userManager.FindByIdAsync(id);
    var logins = user.Logins;
    var rolesForUser = await _userManager.GetRolesAsync(id);

    using (var transaction = context.Database.BeginTransaction())
    {
      foreach (var login in logins.ToList())
      {
        await _userManager.RemoveLoginAsync(login.UserId, new UserLoginInfo(login.LoginProvider, login.ProviderKey));
      }

      if (rolesForUser.Count() > 0)
      {
        foreach (var item in rolesForUser.ToList())
        {
          // item should be the name of the role
          var result = await _userManager.RemoveFromRoleAsync(user.Id, item);
        }
      }

      await _userManager.DeleteAsync(user);
      transaction.Commit();
    }

    return RedirectToAction("Index");
  }
  else
  {
    return View();
  }
}

You'll need to adjust this snippet to your needs, because I don't have an idea how your IdentityUser implementation looks like. Remember to declare the UserManager as needed. An example how you could do this can be found when you create a new project in Visual Studio using Individual Accounts.

Robert Goldwein
  • 5,805
  • 6
  • 33
  • 38
Horizon_Net
  • 5,959
  • 4
  • 31
  • 34
  • Hi Horizon_Net, I have solved this problem by going the same way. Thank you for your explanation :) I actually don't know what is use of IdentityUser. – Franva Jun 01 '14 at 16:16
  • 3
    [IdentityUser](http://msdn.microsoft.com/en-us/library/dn613256(v=vs.108).aspx) is the base class provided by Entity Framework (if you use it) to customize your user object to your needs. The base implementation gives you some basic properties, such as email address or user name, you can work with. With a child class you can extend it with your own properties, such as first name or last name. In most tutorials and examples you will see an implementation called *ApplicationUser*. If you want to know more you definitely should have a look at [Scott Allen's blog](http://odetocode.com/) – Horizon_Net Jun 01 '14 at 16:32
  • 1
    RemoveLoginAsync requires two parameters. Without the .ToList() on logins and rolesForUser I get an exception saying the Enumerable has been modified and cannot continue. I also needed to change `_userManager` to `UserManager` but that seemed to be specific to me. The difference was `UserManager`'s get is `return _userManager ?? HttpContext.GetOwinContext().GetUserManager();` and for some reason without doing that `.FindByIdAsync(id)` would return null. – Brad Jan 14 '15 at 06:28
  • 2
    Also note that if you are going to use `[ValidateAntiForgeryToken]` then you [need to include](http://stackoverflow.com/a/16103150/726127) `@Html.AntiForgeryToken()` in your View – Brad Jan 14 '15 at 06:32
  • Note: `if (rolesForUser.Count() > 0)` is redundant before the role removal `foreach`. – iCollect.it Ltd Jun 09 '15 at 10:49
4

Update for ASP.NET Core 2.0 - hope this saves someone a bit of time

ApplicationDbContext context, 
UserManager<ApplicationUser> userManager, 
ApplicationUser user

var logins = await userManager.GetLoginsAsync(user);
var rolesForUser = await userManager.GetRolesAsync(user);

using (var transaction = context.Database.BeginTransaction())
{
    IdentityResult result = IdentityResult.Success;
    foreach (var login in logins)
    {
        result = await userManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
        if (result != IdentityResult.Success)
            break;
    }
    if (result == IdentityResult.Success)
    {
        foreach (var item in rolesForUser)
        {
            result = await userManager.RemoveFromRoleAsync(user, item);
            if (result != IdentityResult.Success)
                break;
        }
    }
    if (result == IdentityResult.Success)
    {
        result = await userManager.DeleteAsync(user);
        if (result == IdentityResult.Success)
            transaction.Commit(); //only commit if user and all his logins/roles have been deleted  
    }
}
wpqs
  • 657
  • 1
  • 7
  • 18
3
  • Brad's point about requiring @Html.AntiForgeryToken() in views is not necessary if you are using latest versions of ASP.NET - see AntiForgeryToken still required
  • Why not create a SQL trigger for AspNetUsers so deleting a user also deletes the corresponding records for user from AspNetUserRoles and AspNetUserLogins?
  • I need to invoke DeleteUser from a number of places so I added a static method to AccountController (see below). I'm still learning about MVC, so should be grateful for comments, in particular 1) use of IdentityResult as a return code 2) wisdom of extending AccountController in this way 3) approach for putting password (cleartext) into the Model to validate the action (see sample invocation).

     public static async Task<IdentityResult> DeleteUserAccount(UserManager<ApplicationUser> userManager, 
                                                                             string userEmail, ApplicationDbContext context)
    {
         IdentityResult rc = new IdentityResult();
    
        if ((userManager != null) && (userEmail != null) && (context != null) )
        {
            var user = await userManager.FindByEmailAsync(userEmail);
            var logins = user.Logins;
            var rolesForUser = await userManager.GetRolesAsync(user);
    
            using (var transaction = context.Database.BeginTransaction())
            {
              foreach (var login in logins.ToList())
              {
                await userManager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey);
              }
    
              if (rolesForUser.Count() > 0)
              {
                foreach (var item in rolesForUser.ToList())
                {
                  // item should be the name of the role
                  var result = await userManager.RemoveFromRoleAsync(user, item);
                }
              }
              rc = await userManager.DeleteAsync(user);
              transaction.Commit();
            }
        }
        return rc;
    }
    

Sample invocation - form passes the user's password (cleartext) in Model:

        // POST: /Manage/DeleteUser
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteUser(DeleteUserViewModel account)
    {
        var user = await GetCurrentUserAsync();
        if ((user != null) && (user.PasswordHash != null) && (account != null) && (account.Password != null))
        {
            var hasher = new Microsoft.AspNetCore.Identity.PasswordHasher<ApplicationUser>();
            if(hasher.VerifyHashedPassword(user,user.PasswordHash, account.Password)  != PasswordVerificationResult.Failed)
            {
                IdentityResult rc = await AccountController.DeleteUserAccount( _userManager, user.Email, _Dbcontext); 
                if (rc.Succeeded)
                {
                    await _signInManager.SignOutAsync();
                    _logger.LogInformation(4, "User logged out.");
                    return RedirectToAction(nameof(HomeController.Index), "Home");
                }
            }
        }
        return View(account);
    }
Community
  • 1
  • 1
wpqs
  • 657
  • 1
  • 7
  • 18
0

I was looking also for the answer but finally this is what work well for me, even its old post but it may help for someone.

// GET: Users/Delete/5
    public ActionResult Delete(string id)
    {

        using (SqlConnection sqlCon = new SqlConnection(connectionString))
        {
            sqlCon.Open();

            string query = "DELETE FROM AspNetUsers WHERE Id = @Id";
            SqlCommand sqlCmd = new SqlCommand(query, sqlCon);
            sqlCmd.Parameters.AddWithValue("@Id", id);
            sqlCmd.ExecuteNonQuery();
        }

        return RedirectToAction("Index");
    }

    // POST: Users/Delete/5
    [HttpPost]
    public ActionResult Delete(string id, FormCollection collection)
    {
        try
        {
            // TODO: Add delete logic here

            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }
Alik
  • 5
  • 2
  • 1
    Please don't answer just with source code. Try to provide a nice description about how your solution works. See: [stackoverflow.com/help/how-to-answer](https://stackoverflow.com/help/how-to-answer). Thanks! – Matt Ke Feb 25 '19 at 21:32