How to update IdentityUser with custom properties using MVC5 and entity framework

asp.net-mvc c# entity-framework

Question

For user management, I'm utilizing the integrated identity framework, however I'd like to make a few changes to the AspNetUsers database. Each time I've run into a problem so far, the answer has resulted in a new issue.

Call UserManager if I modify the user model, such as by adding a zip code property and matching field to the AspNetUsers table. Although UpdateAsync(user) succeeds, the database's zip code field is not updated.

At least One more SO query has made an effort to handle this. However, the suggested remedies there cause other problems:

1) When trying to attach the user object to a second instance of the UserDbContext, entity framework complains that "An entity object cannot be referenced by more than one instance of IEntityChangeTracker."

2) By disabling proxy construction, the issue described in #1, nevertheless makes the dbcontext fail to load any child objects., is resolved (like AspNetUserLogins, which are rather important).

Accessing the context generated in the Controller would be an alternative solution. Consider the constructor methods of the default AccountController when creating a new ASP.NET web application with the MVC (version 5) template:

 public AccountController()
            : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }

        public AccountController(UserManager<ApplicationUser> userManager)
        {
            UserManager = userManager;
        }

Although the application's database context is created, it cannot be accessed using UserManager due to its 'Store' private property.

It doesn't look like rocket science, therefore my guess is that I am managing or understanding the dbcontext lifecycle fundamentally incorrectly.

In order to save and update AspNetUsers, their related custom properties, and maintain child objects (such AspNetUserLogins), how do I properly access and use the dbcontext?

EDIT ———-

I tried one more thing...

In contrast to the default, I modified the AccountController's constructor:

    public AccountController(UserManager<ApplicationUser> userManager)
    {
       UserManager = userManager;
    }

on this:

    public AccountController(UserManager<ApplicationUser> userManager)
    {
        userDbContext= new UserDbContext();
        UserStore<ApplicationUser> store = new UserStore<ApplicationUser>();
        UserManager<ApplicationUser> manager = new UserManager<ApplicationUser>(store);

        manager.UserValidator = new CustomUserValidator<ApplicationUser>(UserManager);

       // UserManager = userManager;
        UserManager = manager;

    }

in an effort to retain the dbcontext. Later, I try to call the following in the body of a public async Task method:

  var updated = await UserManager.UpdateAsync(user);

  if (updated.Succeeded)
  {
    userDbContext.Entry(user).State = System.Data.Entity.EntityState.Modified;
    await userDbContext.SaveChangesAsync();
  }

The attempt to update the state, however, generates an exception:

"The object layer type "xyz.Models.ApplicationUser" already has a created proxy type. This happens when two or more different models in an AppDomain map the same object layer type."

It's the same dbcontext that was assigned in the constructor, therefore something is wrong.

EDIT #2 ——-

The ApplicationUser model is as follows:

using Microsoft.AspNet.Identity.EntityFramework;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using System.Data.Entity;

namespace xyz.App.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string ZipCode { get; set; }
        public string PasswordResetToken { get; set; }
        public System.DateTime? PasswordResetTokenExpiry { get; set; }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        public ApplicationUser() { }

    }




    public class UserDbContext : IdentityDbContext<ApplicationUser>
    {
        public UserDbContext()
            : base("DefaultConnection")
        {

        }

    }
}

Final revision —————————

Okay, I realized I was phrasing the question incorrectly after some back-and-forth in the comments. Really, I wanted to know how to employ code-first migrations as opposed to database-first migrations. I was raised in the Hibernate school and was accustomed to manually converting objects to tables using XML or Java annotations.

Therefore, because I was only skimming this piece, I missed the crucial migration steps. learnt lesson

1
20
5/23/2017 11:47:17 AM

Accepted Answer

So, as opposed to utilizing code first, you are leveraging databases first. Use code-first is what I advise. Similar to you, I started using databases first. Delete the migrations table from your database if you aren't using code-first approaches. Otherwise, issues will arise.

I strongly suggest using this code-first tutorial, which has been quite helpful to me. You have complete framework integration for authentication and can add custom fields to your identity with ease.

http://blogs.msdn.com/b/webdev/archive/2013/10/16/customizing-profile-information-in-asp-net-identity-in-vs-2013-templates.aspx

10
5/16/2017 2:15:36 PM

Popular Answer

I experienced the same issue. To solve this, all I had to do was take the model's properties I needed to change and save them to an object I had retrieved from the UserManager;

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(ApplicationUser model)
    {
        if (ModelState.IsValid)
        {
            ApplicationUser u = UserManager.FindById(model.Id);
            u.UserName = model.Email;
            u.Email = model.Email;
            u.StaffName = model.StaffName; // Extra Property
            UserManager.Update(u);
            return RedirectToAction("Index");
        }
        return View(model);
    }


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow