Search code examples
c#asp.net-coreentity-framework-coreasp.net-core-identity

Add Collection to user object in ASP.NET Core with Identity


I was playing around with EF Core and ASP.NET Core and stumbled into the following problem. I wanted to add some extra data to the User Object in form of a List. The problem is that the List is never updated.

Here's my DbContext:

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

And now my User Object:

public class HostUser : IdentityUser
{
    [PersonalData]
    public ICollection<GuestUser> GuestUsers { get; set; }
}

And here is the adding of a new GuestUser in the Contoller:

[HttpPost]
    public async Task<IActionResult> Post([FromBody]GuestUser userToInsert)
    {
        if (userToInsert == null)
        {
            return BadRequest();
        }

        var currentUser = await GetCurrentUserAsync();
        if (currentUser == null)
        {
            return Forbid();
        }

        if(currentUser.GuestUsers?.Any(user => user.Id == userToInsert.Id) ?? false)
        {
            return BadRequest();
        }

        if(currentUser.GuestUsers == null)
        {
            currentUser.GuestUsers = new List<GuestUser>();
        }

        currentUser.GuestUsers.Add(userToInsert);
        await userManager.UpdateAsync(currentUser);

        return Ok();
    }

My question is if this is a complete wrong approach and I have to add a DbSet of GuestUser in the DbContext and map it to the user. If this is the case I have no idea how to achieve this. Note: The GuestUser in this case is not another IdentityUser, it's local user data


Solution

  • This is how it might look like:

    Entitties:

    public class HostUser : IdentityUser
    {
        public virtual ICollection<GuestUser> GuestUsers { get; set; }
    }
    
    public class GuestUser
    {
        public int HostUserId { get; set; }
        public virtual HostUser HostUser { get; set; }
    }
    

    DB Context:

    public class ApplicationDbContext : IdentityDbContext<HostUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    
        public DbSet<GuestUser> GuestUsers { get; set; }
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            builder.Entity<HostUser>(
                typeBuilder =>
                {
                    typeBuilder.HasMany(host => host.GuestUsers)
                        .WithOne(guest => guest.HostUser)
                        .HasForeignKey(guest => guest.HostUserId)
                        .IsRequired();
    
                    // ... other configuration is needed
                });
    
            builder.Entity<GuestUser>(
                typeBuilder =>
                {
                    typeBuilder.HasOne(guest => guest.HostUser)
                        .WithMany(host => host.GuestUsers)
                        .HasForeignKey(guest => guest.HostUserId)
                        .IsRequired();
    
                    // ... other configuration is needed
                });
        }
    }
    

    Controller action:

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] GuestUser userToInsert)
    {
        // All checks and validation ...
    
        // You can get the current user ID from the user claim for instance
        int currentUserId = int.Parse(User.FindFirst(Claims.UserId).Value);
    
        // _context is your ApplicationDbContext injected via controller constructor DI 
        userToInsert.HostUserId = currentUserId;
    
        // Save new guest user associated with the current host user
        _context.GuestUsers.Add(userToInsert);
        await _context.SaveChangesAsync();
    
        // If you need to get the current user with all guests
        List<HostUser> currentUser = await _context.Users.Include(host => host.GuestUsers).ToListAsync();
    
        return Ok(currentUser);
    }
    

    Here I provided the full code - how to configure custom Identity based DB context (all needed custom classed inherited from base classes used by IdentityDbContext).