20

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.

Community
  • 1
  • 1
Gojira
  • 2,941
  • 2
  • 21
  • 30
  • Can you please tell me what your problem is; it is still not clear to me. What are you trying to accomplish? – user1477388 Apr 02 '14 at 18:41
  • I am trying to update an ApplicationUser object managed by EntityFramework. calling UserManager.UpdateAsync(user) returns success, but I see that none of the user's custom properties have been updated. – Gojira Apr 02 '14 at 18:55
  • Show me your ApplicationUser model. – user1477388 Apr 02 '14 at 19:17
  • Posted. Mostly the same as the one autogenerated by the template, with the addition of a few properties. – Gojira Apr 02 '14 at 19:24
  • I don't remember GenerateUserIdentityAsync(), did you add that? Other than that, how did you update your database? I assume you are using code first. – user1477388 Apr 02 '14 at 19:27
  • I did, in order to be able to use Google/FB as OAuth providers (e.g., http://blog.beabigrockstar.com/using-google-authenticator-asp-net-identity/). Doing hacky things like: IdentityResult removeSuccess = await UserManager.RemovePasswordAsync(user.Id); IdentityResult addSuccess = await UserManager.AddPasswordAsync(user.Id, model.NewPassword); work. The custom properties (zip code for example) seem to be the sticking point. – Gojira Apr 02 '14 at 19:49
  • Try updating the user properties without any other stuff that you added so you can see what is breaking it. Also, you didn't answer how you updated your database, – user1477388 Apr 02 '14 at 19:50
  • Maybe I'm not understanding what you mean by updating the database... Shouldn't UserManager.UpdateAsync update the database? – Gojira Apr 02 '14 at 19:56
  • I mean update the database schema. Are you using migrations? – user1477388 Apr 02 '14 at 22:22
  • Yes, the original schema was created based on the standard identity layout, then I manually added a few columns to dbo.AspNetUsers. The only entry in the __MigrationHistory table is: "201402211703509_InitialCreate Microsoft.AspNet.Identity.EntityFramework.IdentityDbContext`1[Microsoft.AspNet.Identity.EntityFramework.IdentityUser] 0x1F8B0... 6.0.0-20911" – Gojira Apr 03 '14 at 14:27
  • After you added your custom columns to ApplicationUser, did you add the migration via the console? – user1477388 Apr 03 '14 at 17:49
  • No. I think I'm figuring out what happened. The database and the project were created independently. I incorrectly assumed that EF was using some kind of convention over configuration. Looks like what's happening is they're out of sync, similar to this issue: http://stackoverflow.com/questions/13238203/automatic-migrations-for-asp-net-simplemembershipprovider – Gojira Apr 03 '14 at 20:13
  • Ok, I'm really confused. It appears migrations are just for keeping code in sync with data structures... with EF5, shouldn't I be able to manually add a property to a model object and manually create that column and have EF pick it up? Or is there some Hibernate-style config file behind the scenes somewhere I need to edit? (I realize this is a veer from the original question) – Gojira Apr 03 '14 at 20:21

2 Answers2

35

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);
    }
Josh
  • 3,442
  • 2
  • 23
  • 24
10

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

user1477388
  • 20,790
  • 32
  • 144
  • 264