Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.

like image 476
Gojira Avatar asked Apr 02 '14 17:04

Gojira


People also ask

What is Identity Framework in ASP net Core?

ASP.NET Core Identity provides a framework for managing and storing user accounts in ASP.NET Core apps. Identity is added to your project when Individual User Accounts is selected as the authentication mechanism. By default, Identity makes use of an Entity Framework (EF) Core data model.


1 Answers

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);
    }
like image 96
Josh Avatar answered Sep 22 '22 13:09

Josh