Search code examples
c#asp.net-core-mvcasp.net-core-identity

Changing table name of default ASP.NET IdentityUser table not working


I'm upgrading an ASP.NET MVC website from .NET 4.7.2 to ASP.NET Core 8. The old site used Entity Framework 6.4 and Microsoft.AspNet.Identity.Core 2.2. The new site uses Entity Framework Core 8.0 and Microsoft.AspNetCore.Identity.EntityFramework 8.0.

There is an existing database with tables from the earlier Identity framework. The table names and columns are nearly identical:

IdentityRole, IdentityUser, IdentityUserClaim, IdentityUserLogin, IdentityUserRole

However, IdentityUser does not contain the new ConcurrencyStamp field, while also containing a custom field LastLoginTime.

I scaffold reversed-engineered the database to create new Entity Framework Core models, and I think I've correctly determined I should not use the generated Identity... classes, as these seem to be provided by the identity framework (right?)

So that leaves two needs:

  1. Customize IdentityUser to include the custom LastLoginTime column
  2. Ensure default Identity classes are mapped to existing table names

Reading answers such as (How can I change the table names when using ASP.NET Identity?) suggests I should be able to simply call .ToTable("IdentityUser") from OnModelCreating(ModelBuilder modelBuilder).

ApplicationDbContext:

public partial class ApplicationDbContext : IdentityDbContext<IdentityUser> 
{
    public ApplicationDbContext(DbContextOptions options) : base(options) {}

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    
        modelBuilder.Entity<ApplicationUser>(entity =>
        {
            entity.ToTable("IdentityUser");
            entity.Property(e => e.LastLoginTime).HasColumnType("datetime");
            entity.Property(e => e.LockoutEnd).HasColumnName("LockoutEndDateUtc").HasColumnType("datetime)");
        });
    }
}

ApplicationUser:

public class ApplicationUser : IdentityUser
{
    public DateTime? LastLoginTime { get; set; }

    public ApplicationUser() : base() 
    { }

    public ApplicationUser(string userName) : base(userName) 
    { }
}

HomeController:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;

    public HomeController(ILogger<HomeController> logger, SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
    {
        _logger = logger;
        _signInManager = signInManager;
        _userManager = userManager;
    }

    public async Task<ActionResult> Login(LoginViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        var identityUser = await _signInManager.UserManager.FindByNameAsync(model.UserName);
        // EXCEPTION
    }
}

Program.cs:

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
        builder.Services.AddMvcCore().AddJsonOptions(o =>
        {
            o.JsonSerializerOptions.PropertyNamingPolicy = null;
            o.JsonSerializerOptions.DictionaryKeyPolicy = null;
        });

        builder.Services.AddDistributedMemoryCache();

        builder.Services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(5);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });

        builder.Services.AddDbContext<ApplicationDbContext>(options =>
            options.UseMySQL(builder.Configuration.GetConnectionString("Default") ?? ""));

        builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {
            options.Password.RequiredLength = 8;
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
            options.Lockout.MaxFailedAccessAttempts = 5;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        builder.Services.AddHttpContextAccessor();

        var app = builder.Build();

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();
        app.UseSession();

        app.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        AppDomain.CurrentDomain.SetData("ContentRootPath", builder.Environment.ContentRootPath);
        AppDomain.CurrentDomain.SetData("WebRootPath", builder.Environment.WebRootPath);

        app.Run();
    }
}

When I test my controller, I see:

MySql.Data.MySqlClient.MySqlException (0x80004005): Table 'MyDb.AspNetUsers' doesn't exist
[...omitted]

at Microsoft.AspNetCore.Identity.UserManager`1.FindByNameAsync(String userName)

Why is FindByNameAsync searching AspNetUsers instead of the table I specified via ToTable("IdentityUser")?


Solution

  • Your ApplicationDbContext should derive from ApplicationUser; not IdentityUser

    ApplicationDbContext : IdentityDbContext<IdentityUser>
    

    should be

    ApplicationDbContext : IdentityDbContext<ApplicationUser>