3

I'm currently developing a web application. I'm using Identity for the authentication and roles of my user.

I want each user in my application to have an associated "Institution". This Institution contains a Name and a Description. Here is my IdentityUser class:

    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in   CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        [Display(Name="Institution")]
        public Institution Institution { get; set; }
    }

When I update my database, the Seed method is executed, and this one, I'm creating a User with the "admin" roles and I associate an Institution. Here is my Seed method:

if (!context.Users.Any(u => u.UserName == "mxfragz"))
{
    var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
    roleManager.Create(new IdentityRole("admin"));

    var store = new UserStore<ApplicationUser>(context);
    var manager = new UserManager<ApplicationUser>(store);
    var user = new ApplicationUser { UserName = "mxfragz" };
    user.Institution = new Institution() { Name = "London", Description="Description" };
    manager.Create(user, "password");
    manager.AddToRole(user.Id, "admin");
}

My problem is that, when I'm creating a new User in my web application, I can't find a way to associate an existing Institution (here only "London" is created). With what I've done so far, when I create a user, I get the ID of the selected Institution and find an existing Institution in order to associate it with the Institution property defined in my user. When I do so, instead of associating the existing Institution that has been found, entity framework creates a new one, and associate it to my User. I end up with 2 different Institutions with the same Name and Description. Here is my code:

public async Task<ActionResult> Create(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.Username, Email = string.Empty };
        int selected = int.Parse(model.SelectedInstitution);
        user.Institution = new InstitutionsDb().Institutions.Where(x => x.Id == selected).First();
        IdentityResult result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
            UserManager.AddToRole(user.Id.ToString(), roleManager.Roles.Where(x => x.Id == model.SelectedRole.ToString()).First().Name);

            return RedirectToAction("Index", "Users");
        }
        else
        {
            AddErrors(result);
        }
    }

    model.Roles = GetRoles();
    model.Institutions = GetInstitutions();
    return View(model);
}

I found several topics about using the Attach method, but even when I tried with it, it didn't work. Am I doing something wrong ? Or is there any way to do what I want to do ?

4
  • 1
    Don't set user.Institution but also expose the foreign key in your model, (user.InstitutionId?) and set its value. Commented Dec 14, 2014 at 21:07
  • I just tried it, and it works ! But, is there a way I can associate entities directly instead of just the ID ? Because with this solution, I'm not able to actually access the Institution from the User (user.Institution.Name). Commented Dec 14, 2014 at 21:23
  • Only if somehow you can attach the Institution to the UserManager first, but I don't know if that's possible (I don't know this UserManager very well). Commented Dec 14, 2014 at 21:27
  • An alternative could be to do it the other way around and add ApplicationUser objects to an ApplicationUsers collection (if present) of an attached Institution. Commented Dec 14, 2014 at 21:30

1 Answer 1

1

The foreign key relationship needs to be exposed through a virtual collection on the Institution along with the actual foreign key value on the ApplicationUser.

public class ApplicationUser : IdentityUser
{
    //...

    public int InstitutionId { get; set; }

    [Display(Name="Institution")]
    [ForeignKey("InstitutionId")] //This attribute isn't strictly necessary but adds clarity
    public Institution Institution { get; set; }
}

public class Institution
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

EF will automatically attach the associated Institution on the virtual property based on the InstitutionId shown below.

I would suggest just adding the DbSet<Institutions> to the ApplicationDbContext rather than it's own InstitutionsDb context, this may be a part of your issue because the UserManager is bound only to the ApplicationDbContext or which ever context you have configured int he IdentityConfig.cs file.

public async Task<ActionResult> Create(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.Username, Email = string.Empty };
        int selected = int.Parse(model.SelectedInstitution);

        var context = HttpContext.GetOwinContext().Get<ApplicationDbContext>()

        //Set the id
        user.InstitutionId = context.Institutions.Where(x => x.Id == selected).First().Id;          

        IdentityResult result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new IdentitiesDb()));
            UserManager.AddToRole(user.Id.ToString(), roleManager.Roles.Where(x => x.Id == model.SelectedRole.ToString()).First().Name);

            return RedirectToAction("Index", "Users");
        }
        else
        {
            AddErrors(result);
        }
    }

    model.Roles = GetRoles();
    model.Institutions = GetInstitutions();
    return View(model);
}

This should allow you to call user.Institution.Name when you retrieve the ApplicationUser from the UserManager

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.