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

asp.net-mvc c# entity-framework

Question

I am using the built in identity framework for user management, and would like to add a few customizations to the AspNetUsers table. So far the solution to each problem I've encountered causes another problem.

If I make a change to the user model (say, by adding a zip code property and matching field in the AspNetUsers table), then call UserManager.UpdateAsync(user), it succeeds but does not update the zip code field in the database.

At least one other SO question has tried to deal with this. But the suggested fixes there break other things:

1) Creating another instance of the UserDbContext and trying to attach the user object causes entity framework to complain that “An entity object cannot be referenced by multiple instances of IEntityChangeTracker”

2) Turning off proxy creation gets rid of the problem listed in #1, but causes the dbcontext to not load child objects (like AspNetUserLogins, which are rather important).

Another solution would be to access the context created in the Controller. Consider the default AccountController's constructor methods with a new ASP .NET Web Application using the MVC (version 5) template:

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

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

The application DB Context is created, but there is no way to access it via the UserManager (because the 'Store' private property of UserManager).

This doesn't seem like rocket science, so my guess is that I am doing something basically wrong around handling/understanding the dbcontext lifecycle.

So: how do I correctly access/use the dbcontext to save and update AspNetUsers, associated custom properties, and preserve child objects (like AspNetUserLogins)?

EDIT -------

One more thing I tried...

I updated the AccountController's constructor from the default:

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

to 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 attempt to hang on to the dbcontext. Later, in the body of a public async Task method, I attempt to call:

  var updated = await UserManager.UpdateAsync(user);

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

However, the attempt to update the state throws an exception:

"There is already a generated proxy type for the object layer type 'xyz.Models.ApplicationUser'. This occurs when the same object layer type is mapped by two or more different models in an AppDomain."

That doesn't seem right... it's the same dbcontext assigned in the constructor.

EDIT #2 -----

Here is the ApplicationUser model:

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 edit ----------------------------

Ok, after some back and forth in the comments, I realized I was asking the question in the wrong way. My question was really: How to use Code-first rather than Database-first migrations. Coming from the Hibernate school, I had been used to manually mapping objects to tables via XML or annotations in Java.

So, in skimming this article, I missed the important steps around migration. Lesson learned.

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

Accepted Answer

So, you are using database-first instead of code-first. My recommendation is to use code-first. I, like you, started out using database first, instead. If you aren't using code-first, then delete the migrations table in your database. Otherwise, it will cause problems.

If possible, I recommend following this code-first tutorial, which has helped me greatly. You can easily add custom fields to your identity and have full integration with the framework as far as authentication goes.

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 faced the same problem. An easy way to overcome this was just to take the properties I wanted to update from the model and save them back to an object pulled 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