Search code examples
asp.netentity-frameworkasp.net-identitydomain-driven-design

Proper way to use strongly-typed Ids with IdentityUser in ASP.NET?


I'm following clean architecture and Domain Driven Design in my project. I'm using strongly typed Id for my User entity. User entity is extended from ASP.NET IdentityUser class. But my issue is when I use a Strongly typed Id for my IdentityUser, I also have to use same type for IdentityRole type. IdentityRole<UserId>. I'm thinking whether this is correct or not. Because IdentityRole doesn't need to have UserId type right?

When I need to create a User role it looks like this,

new IdentityRole<UserId>(UserRoles.User) { Id = new UserId(Guid.NewGuid()) }
new IdentityRole<UserId>(UserRoles.Admin) { Id = new UserId(Guid.NewGuid()) }

Is there any other proper way to use Strongly typed Ids with IdentityUser? Or is there anything I'm doing wrong?

User Entity

public class User : IdentityUser <UserId>
{
    public override UserId Id { get; set; }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    
    private User(string firstName, string lastName, string email, string passwordHash)
    {
        // assignments...
    }
    
    public static Result<User> Create(string firstName, string lastName, string email, string password)
    {
        // user creation code...
    }
}

UserId

public record UserId (Guid Value);  

IdentityDbContext

public class ApplicationUserIdentityDbContext : IdentityDbContext<User, IdentityRole<UserId>,UserId>
{
    public ApplicationUserIdentityDbContext(DbContextOptions<ApplicationUserIdentityDbContext> options) 
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.HasDefaultSchema("User");

        builder.Entity<User>()
            .Property(u => u.Id)
            .HasConversion(
                id => id.Value, 
                value => new UserId(value));
 
        builder.Entity<IdentityRole<UserId>>()
            .Property(userRole => userRole.Id)
            .HasConversion(
                userRoleId => userRoleId.Value, 
                value => new UserId(value));
    }
}

Program.cs

builder.Services.AddDbContext<ApplicationUserIdentityDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DBConnection")));

builder.Services.AddIdentityCore<User>()
    .AddRoles<IdentityRole<UserId>>()
    .AddEntityFrameworkStores<ApplicationUserIdentityDbContext>();

PS: I'm using AddIdentityCore instead AddIdentity because I've two IdentityUser types in my system. Because I can't call AddIdentity twice I'm using AddIdentityCore.


Solution

  • I have got a proper solution for my issue.

    public class User : IdentityUser
    {
        [NotMapped]
        public new UserId Id 
        { 
           get => new UserId(new Guid(base.Id).ToString());  
           private set => base.Id = value.Value.ToString(); 
        }
    
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        
        private User(string firstName, string lastName, string email, string passwordHash)
        {
            // assignments...
        }
        
        public static Result<User> Create(string firstName, string lastName, string email, string password)
        {
            // user creation code...
        }
    }
    

    with this approach I can use strongly-typed id with my Entity following clean architecture and Domain Drive Design. using new keyword for Id field, I'm hiding my base class Id field to external. Because I'm using NotMapped annotation it won't map this strongly-typed Id column to database.