Search code examples
c#asp.net-coreasp.net-web-apiasp.net-identityentity-framework-migrations

Method userManager.FindByEmailAsync() returns object with empty UserName and Email


I had to extend IdentityUser model (and AspNetUser table of course) with some extra field (RegistrationDate) so I created ExtendIdentityUser model class. It works nice.

Then, I needed to change some user default fields (UserName and Email) to required ones. After this change method await userManager.FindByEmailAsync("[email protected]"); started returns user object with empty username and email fields. Rest of them are properly fullfileed, even NormalizedUserName and NormalizedEmail. Before making UserName and Email to required it worked properly.

My custom model class looks like:

public class ExtendedIdentityUser : IdentityUser
{
    public override string UserName { get; set; } = string.Empty;
    public override string Email { get; set; } = string.Empty;
    public DateTime RegistrationDate { get; set; }
}

My custom map model class is:

public class ExtendedIdentityUserMap : IEntityTypeConfiguration<ExtendedIdentityUser>
{
    public void Configure(EntityTypeBuilder<ExtendedIdentityUser> builder)
    {
        // Properties
        builder.Property(u => u.UserName).HasColumnName("UserName").IsRequired();
        builder.Property(u => u.Email).HasColumnName("Email").IsRequired();
        builder.Property(u => u.RegistrationDate).IsRequired();
        
        // Default values
        builder.Property(u => u.RegistrationDate).HasDefaultValueSql("GETDATE()")
    }
}

ApplicationDbContext was also updated with my map class:

public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options)
{
    public DbSet<ExtendedIdentityUser> ExtendedIdentityUsers { get; set; }
    // another tables
    
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.ApplyConfiguration(new ExtendedIdentityUserMap());
        // another mappings
    }
}

Generated migrations code looks good:

protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<string>(
                name: "UserName",
                table: "AspNetUsers",
                type: "nvarchar(256)",
                maxLength: 256,
                nullable: false,
                defaultValue: "",
                oldClrType: typeof(string),
                oldType: "nvarchar(256)",
                oldMaxLength: 256,
                oldNullable: true);

            migrationBuilder.AlterColumn<string>(
                name: "Email",
                table: "AspNetUsers",
                type: "nvarchar(256)",
                maxLength: 256,
                nullable: false,
                defaultValue: "",
                oldClrType: typeof(string),
                oldType: "nvarchar(256)",
                oldMaxLength: 256,
                oldNullable: true);
        }

Changes are appiled successfully to my local DB - columns UserName and Email became non nullable. Data is there. But calling await userManager.FindByEmailAsync("[email protected]"); returns user object with empty username and email fields.

My question is: what am I missing? Why username and email fields are empty for my user object?

If it's possible I would like to avoid implement own userManager as a solution.

@EDIT I had investigated it deeply, and I discovered that property Users in Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore already contains list of users without filled email and username fields. That's why the method FindByEmailAsync() from userManager returns not completed data as well, because it uses UserStore class for obtaining userData.


Solution

  • Please check your Identity Configuration code, you are using the following code to configure the Identity, right?

    builder.Services.AddDefaultIdentity<ExtendedIdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();  
    

    After testing it with your custom model class and ApplicationDbContext, I reproduce the problem, when query the data using usermanager, the result is null, even though the data exists.

    As you said, the issue relates the UserStore, in the deeply, it might still using the IdentityUser.

    To solve this issue, you can modify the ApplicationDbContext as below: use IdentityDbContext<ExtendedIdentityUser>:

    public class ApplicationDbContext : IdentityDbContext<ExtendedIdentityUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
        public DbSet<WebApplication5.Models.Product> Product { get; set; } = default!;
    
        public DbSet<ExtendedIdentityUser> ExtendedIdentityUsers { get; set; }
        // another tables
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            builder.ApplyConfiguration(new ExtendedIdentityUserMap());
            // another mappings
        }
    }
    

    It will specify the ExtendedIdentityUser class as the default user class. So, when query the data via usermanager, the result as below:

    success

    Reference link: Custom user data.