MVC5/EF6: Object cannot be deleted because it was not found in the ObjectStateManager?

3.6k Views Asked by At

I have the following HttpPost Delete() method which was working in my MVC5 app. To the best of my knowledge nothing relating to this controller, the View, or even the model has changed.

    // POST: Admin/UserManagement/Delete/5
    [HttpPost, ActionName("DeleteConfirmed")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> DeleteConfirmed(string id)
    {
        ApplicationUser applicationUser = db.Users.Find(id);
        if (applicationUser == null)
        {
            ModelState.AddModelError("", "Failed to find User ID for deletion.");
        }
        else
        {
            IdentityResult result = await UserManager.DeleteAsync(applicationUser);
            if (result.Succeeded)
            {
                await db.SaveChangesAsync();
                return RedirectToAction("Index", "UserManagement");
            }
            else
            {
                ModelState.AddModelError("", "Failed to Delete User.");
                var errors = string.Join(",", result.Errors);
                ModelState.AddModelError("", errors);
            }
        }

        return View(applicationUser);
    }

UserManager Pieces:

[CustomAuthorization(myRoles = "Admin")]
public class UserManagementController : Controller
{
    protected ApplicationDbContext db { get; set; }
    private ApplicationUserManager _userManager;

    public UserManagementController()
    {
        this.db = new ApplicationDbContext();
    }

    public UserManagementController(ApplicationUserManager userManager)
    {
        UserManager = userManager;
    }

    public ApplicationUserManager UserManager
{
        get
        {
            return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
        private set
        {
            _userManager = value;
        }
    }

When my code reaches IdentityResult result = await UserManager.DeleteAsync(applicationUser) it immediately jumps down to my Dispose() method below, and instead of the Index View loading, I am given: Server Error in '/' Application. The object cannot be deleted because it was not found in the ObjectStateManager.

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

            if (UserManager != null)
            {
                UserManager.Dispose();
                UserManager = null;
            }
        }
        base.Dispose(disposing);
    }

Can someone tell me where I am going wrong? I have never seen this error before. Previously this DeleteConfirmed() code was working exactly as intended.

EDIT:

Startup.cs:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartupAttribute(typeof(PROJECT.Startup))]
namespace PROJECT
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

EDIT2:

CustomAuthorization.cs (Helper):

public class CustomAuthorization : AuthorizeAttribute
{
    public string myRoles { get; set; }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var userAuthInfo = HttpContext.Current.User;

        if (userAuthInfo != null)
        {
            if (!userAuthInfo.Identity.IsAuthenticated)
            {
                string returnUrl = filterContext.HttpContext.Request.RawUrl;
                filterContext.Result = new RedirectResult("/Account/Login?returnUrl=returnUrl");
                return;
            }

            string[] roles = myRoles.Split(',');


            var userAuth = false;

            foreach (string role in roles)
            {

                if (userAuthInfo.IsInRole(role))
                {
                    userAuth = true;
                    break;
                }
            }

            if (!userAuth)
            {
                var result = new RedirectResult("/Home?auth=0");

                filterContext.Result = result;
            }
        }
    }
}

EDIT3

Startup.Auth.cs in /App_Start/:

public partial class Startup
{
    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    }
}

Solution as pointed out by haim770 was that I was mistakenly using 2 DbContext references within my DeleteConfirmed() Action Method.

Changed ApplicationUser applicationUser = db.Users.Find(id) to correctly be async and everything works as intended: ApplicationUser applicationUser = await UserManager.FindByIdAsync(id);

Thanks to everyone who took the time to assist!

1

There are 1 best solutions below

0
On BEST ANSWER

The problem is that you're actually using 2 DbContext here and they cannot track each other's Entities.

You have to change your Controller code to have both db and UserManager share the same context instance reference:

public UserManagementController()
{
    this.db = context.Get<ApplicationDbContext>();
}