So I'm building an application on top of an existing database to which I have limited rights to change (the idea is to make as few changes to the DB schema as possible). This is an MVC 5 application attempting to use the Identity system with a custom user store to a MySQL database.
Problem: I'm getting the following exception when attempting to register a user via the auto-generated AccountController in the register method:
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
System.InvalidOperationException: The entity type ApplicationUser is not part of the model for the current context. at System.Data.Entity.Internal.InternalContext.UpdateEntitySetMappingsForType(Type entityType) at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) at System.Data.Entity.Internal.Linq.InternalSet
1.Initialize() at System.Data.Entity.Internal.Linq.InternalSet
1.get_InternalContext() at System.Data.Entity.Infrastructure.DbQuery1.System.Linq.IQueryable.get_Provider() at System.Data.Entity.QueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable
1 source, Expression1 predicate, CancellationToken cancellationToken) at System.Data.Entity.QueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable
1 source, Expression1 predicate) at Microsoft.AspNet.Identity.EntityFramework.UserStore
6.d__6c.MoveNext()
What I've tried:
- I've tried changing the Account controller's UserManager instantiation from
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
tonew ApplicationUserManager(new UserStoreService(new Entities()));
This solves the immediate problem and lets me register. HOWEVER, this causes problems later on when I try to reset a password and I get another issue that I haven't been able to solve where I get an invalid user token (although I can confirm that the user tokens work correctly when I use the HttpContext.GetOwinContext... version of the UserManager - There are several posts regarding changing the connection string from the autogenerated one like this:
<add name="Entities" connectionString="metadata=res://*/Models.tools.csdl|res://*/Models.tools.ssdl|res://*/Models.tools.msl;provider=MySql.Data.MySqlClient;provider connection string="server=localhost;user id=user;password=***;persistsecurityinfo=True;database=db"" providerName="System.Data.EntityClient" />
to a normal connection string like this:
<add name="Entities" connectionString="server=localhost;user id=user;password=***;persistsecurityinfo=True;database=db" providerName="MySql.Data.MySqlClient" />
. This explodes pretty quickly with the unintentional code first exception. And then a few other problems on down the road that seemed to spiral (after fixing issues with no declared Keys on the tables, etc.).I am open to suggestions on this, but would prefer to not have to go this route.
Below is the code relevant to the setup. Any ideas what else I could be missing here? Or is the only way to solve this with the connection string idea?
Thanks!
Setup I'm using database first (EDMX) to an existing MySQL database. Following along with this to change the primary key for users to an int, I have a custom userstore service to attach to my database and user table.
DbContext setup (I have modified from the autogenerated file in attempt to work with the Identity system):
public partial class Entities : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>// DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public static Entities Create()
{
return new Entities();
}
//DbSets are here
}
ApplicationUser.cs:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim> // IUser<int>//IdentityUser
{
//custom properties are here
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
ApplicationUserManager.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{ }
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStoreService(context.Get<Entities>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
manager.EmailService = new EmailService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(dataProtectionProvider.Create("ASP.NET Identity")) { TokenLifespan = TimeSpan.FromHours(24) };
}
return manager;
}
}
UserStoreService.cs
public class UserStoreService : UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim> //, IUserStore<ApplicationUser, int>, IUserPasswordStore<ApplicationUser, int>, IUserEmailStore<ApplicationUser, int>, IUserLockoutStore<ApplicationUser, int>, IUserSecurityStampStore<ApplicationUser, int>
{
private Entities _db; // = new Entities();
public UserStoreService(Entities db) : base(db)
{
_db = db;
}
public override Task CreateAsync(ApplicationUser user)
{
var profile = new ffs_profile {
//set props here
};
_db.ffs_profile.Add(profile);
return _db.SaveChangesAsync();
}
public async override Task<ApplicationUser> FindByNameAsync(string userName)
{
var profile = await _db.ffs_profile.Where(u => u.email == userName).FirstOrDefaultAsync();
ApplicationUser user = null;
if (profile != null)
user = ToApplicationUser(profile);
return user;
}
private ApplicationUser ToApplicationUser(ffs_profile profile)
{
return new ApplicationUser
{
//set properties here
};
}
public override Task<string> GetPasswordHashAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentException("null user");
}
return Task.FromResult(user.PasswordHash);
}
public override Task<bool> HasPasswordAsync(ApplicationUser user)
{
return Task.FromResult(user.PasswordHash != null);
}
public override Task SetPasswordHashAsync(ApplicationUser user, string passwordHash)
{
return Task.Run(() =>
{
if (passwordHash == null)
throw new ArgumentNullException("passwordHash");
if (string.IsNullOrWhiteSpace(passwordHash))
throw new ArgumentException("passwordHash cannot be null, empty, or consist of whitespace.");
user.PasswordHash = passwordHash;
});
}
public override async Task<ApplicationUser> FindByIdAsync(int userId)
{
var profile = await _db.ffs_profile.Where(u => u.profile_id == userId).FirstOrDefaultAsync();
ApplicationUser user = null;
if (profile != null)
user = ToApplicationUser(profile);
return user;
}
public override Task<string> GetSecurityStampAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult<string>(user.SecurityStamp);
}
public override Task SetSecurityStampAsync(ApplicationUser user, string stamp)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
user.SecurityStamp = stamp;
return Task.FromResult<int>(0);
}
}
And finally, the relevant portions of the account controller:
public class AccountController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
{
UserManager = userManager;
SignInManager = signInManager;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
// other autogenerated methods
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
//set props here
};
try
{
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
}
return View(model);
}
}