Search code examples
c#asp.net-mvcentity-frameworkasp.net-identity-2

Reading a detail table to ASP.NET MVC 5 Identity 2


I want to have my IdentityUser derived class to reference some data present on a detail table. In order to do so I created an extended IdentityUser that has a reference to an external detail table, and configured the IdentityDbContext accordingly so that the corresponding table is created.

public class ApplicationUser : IdentityUser
{
    public ICollection<PublishingContext> PublishingContexts { get; set; }

    public ApplicationUser()
    {
        PublishingContexts = new List<PublishingContext>();
    }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

public class PublishingContext
{
    public string UserId { get; set; }
    public string Name { get; set; }
    public string Uri { get; set; }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    public virtual IDbSet<PublishingContext> PublishingContexts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ApplicationUser>()
            .HasMany(u => u.PublishingContexts).WithRequired().HasForeignKey(ul => ul.UserId);

        modelBuilder.Entity<PublishingContext>()
            .HasKey(c => new { c.UserId, c.Name });
    }
}

The problem is that even if the PublishingContexts table gets created and I manually fill some data onto it, I cannot manage to read the corresponding values back while logging in. I think that I need to configure the new table somewhere else on the system, but I'm currently stuck. Any suggestions?


Solution

  • I think I figured it out. You need to define a custom UserStore that will redefine the FindBy methods, including the details table accordingly.

    public class ApplicationUserStore : UserStore<ApplicationUser>
    {
        public ApplicationUserStore() : this(new IdentityDbContext())
        {
            DisposeContext = true;
        }
    
        public ApplicationUserStore(DbContext context) : base(context)
        {
        }
    
        public override Task<ApplicationUser> FindByIdAsync(string userId)
        {
            return GetUserAggregateAsync(u => u.Id.Equals(userId));
        }
    
        public override Task<ApplicationUser> FindByNameAsync(string userName)
        {
            return GetUserAggregateAsync(u => u.UserName.ToUpper() == userName.ToUpper());
        }
    
        Task<ApplicationUser> GetUserAggregateAsync(Expression<Func<ApplicationUser, bool>> filter)
        {
            return Users.Include(u => u.Roles)
                .Include(u => u.Claims)
                .Include(u => u.Logins)
                .Include(u => u.PublishingContexts)
                .FirstOrDefaultAsync(filter);
        }
    }
    

    This is taken directly from Identity 2 sources. Basically I just added the .Include clause on the GetUserAggregateAsync method, which I had to redefine because it is private on the base class. Once you do that you will have to tell the system to use your userstore, by changing it on the IdentityConfig.cs file in App_Start

    var manager = new ApplicationUserManager(new ApplicationUserStore(
        context.Get<ApplicationDbContext>()));