Bill Kron
Bill Kron

Reputation: 113

How to get a subset of Users from ASP.NET Identity 2

Using a slightly modified version of the default ASP.NET MVC 5 template (with Individual Accounts), I am trying to get a subset of users based on an intermediary table. I have already built up an administration UI that can return a list of all users, but now I need to limit the set of users returned based on the currently logged in user's access privileges defined in the intermediary table.

Essentially, each user will have access to 1 or more clinics, so there will be one record for each clinic to which they have access.

If the currently logged in user belongs to a given role (e.g., "Clinic Admin"), then they should have the ability to retrieve a list of any users who belong to any of the clinics to which they have access.

Can anyone help point me in the right direction? This is my first Anything.NET application, so please feel free to explain like I'm five. :-)

Thank you in advance for any help you can offer.

Additional information:

Here is the intermediary table's class (ClinicUser):

[Table("clinic_users")]
public class ClinicUser
{
    [Key]
    public virtual ApplicationUser ApplicationUsers { get; set; }

    [Required]
    public string Id { get; set; }

    [Required]
    public System.Guid provider_id { get; set; }

    [Required]
    public System.Guid health_system_id { get; set; }

    [Required]
    public System.Guid clinic_id { get; set; }
}

Here is my ApplicationUser class:

public class ApplicationUser : IdentityUser
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName
    {
        get { return FirstName + " " + LastName; }
    }

    [ForeignKey("ClinicUsers")]
    public override string Id
    {
        get
        {
            return base.Id;
        }
        set
        {
            base.Id = value;
        }
    }

    public virtual ClinicUser ClinicUsers { get; set; }

    public IEnumerable<SelectListItem> RolesList { get; set; }

    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
        userIdentity.AddClaims(ClinicClaimsProvider.GetClaims(userIdentity));
        return userIdentity;
    }
}

In case it wasn't clear, what I'm really trying to do is narrow the list of ApplicationUsers to return only the list of users to which I have access to based on the clinics we have have in common.

If I were writing this as a SQL query, this would be one way to accomplish what I want (I just can't seem to quite get what I want with LINQ):

SELECT *
FROM AspNetUsers au
WHERE Id IN (
    SELECT Id
    FROM clinic_users
    WHERE clinic_id IN (
            SELECT clinic_id
            FROM clinic_users
            WHERE Id = 'CurrentUserId'
            )
    )

Upvotes: 1

Views: 249

Answers (2)

Bill Kron
Bill Kron

Reputation: 113

I solved this a while back, but I thought I had better come back here and update my question with an answer, in case this might help someone else.

I updated my Clinic and ClinicUser classes accordingly:

Clinic.cs

[Table("clinics")]
public class Clinic
{
    [Key]
    public System.Guid ClinicId { get; set; }

    public List<ClinicUser> ClinicUsers { get; set; }
}

ClinicUser.cs

[Table("clinic_users")]
public class ClinicUser
{
    [Key, Column(Order = 0)]
    public string UserId { get; set; }

    [Key, Column(Order = 1)]
    public System.Guid ClinicId { get; set; }

    [ForeignKey("UserId")]
    public virtual ApplicationUser ApplicationUser { get; set; }

    [ForeignKey("ClinicId")]
    public Clinic Clinic { get; set; }
}

Also, I updated the following excerpt of my ApplicationUser class from this:

[ForeignKey("ClinicUsers")]
public override string Id
{
    get
    {
        return base.Id;
    }
    set
    {
        base.Id = value;
    }
}

public virtual ClinicUser ClinicUsers { get; set; }

to this:

public List<ClinicUser> ClinicUsers { get; set; }

Finally, in my ApplicationUsersController's Index() action, I was able to use this:

   public async Task<ActionResult> Index()
    {
        if (User.IsInRole("Admin")) return View(await UserManager.Users.ToListAsync());

        var userId = User.Identity.GetUserId();

        //Get the Ids of the current user's clinics
        var userClinics = db.ClinicUsers.Where(cu => cu.UserId == userId).Select(cu => cu.ClinicId).ToList();

        //Get all userIds of the user at the current user's clinics 
        var clinicUserIds = db.ClinicUsers.Where(cu => userClinics.Contains(cu.ClinicId)).ToList().Select(cu => cu.UserId);

        var users = UserManager.Users.Where(u => clinicUserIds.Contains(u.Id));

        return View(await users.ToListAsync());
    }

In essence, if the user has the "Admin" role, then they will get a list of all users in the database. If they aren't, they will only get a list of the users that also belong to the clinics they have in common.

It may not be perfect, but it works. If anyone has any suggestions on how to improve this, I would be glad to hear it.

Again, my thanks to Archil (https://stackoverflow.com/users/4089212/archil-labadze) for his helpful responses.

Upvotes: 1

Archil Labadze
Archil Labadze

Reputation: 4325

First of all do not user much properties in ApplicationUser class, you can manage user profiles table and connect it with application user class, so you can put lot of information about user in profile table. Next task is organize table of clinics, branches etc... and asociate application users with them. Next you have 2 ways: 1. asociate application users with clinics or branches. or 2. Manage them with roles.

Here is example with Application users:

[Table("Clinics")]
public class Clinic
{
    [Key]
    public string Id { get; set; }

    public virtual ICollection<ClinicUser> ClinicUsers { get; set; }
}

[Table("ClinicUsers")]
public class ClinicUser
{
    [Key]
    [Column(Order = 0)]
    public string ClinicId { get; set; }

    [Key]
    [Column(Order = 1)]
    public string UserId { get; set; }
}

So next you need Other ViewModels to display them hope this help.

UPDATE

// GET: ClinicUsers by Clinic
    public async Task<ActionResult> ViewCurrentClinicUsers(string id) // This is clinis ID
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Clinic clinic = await db.clinic.FindAsync(id); // Get Selectedclinic
        if (clinic == null)
        {
            return HttpNotFound();
        }
        ClinicUsers model = new ClinicUsers() // ClinicUsers model
        {
            clinic = clinic, // View Currentclinic
            ClinicUsers = await db.ClinicUsers.Were(x => x.clinicid == clinic.id)ToListAsync()) // Get Users that asigned to current clinic
        };
        return View(model);
    }

UPDATE 2 And Finaly if you want display clinics were is assigned current loged user

// GET: Clinics by currentuser
    public async Task<ActionResult> ViewClinicsWithCurrentUserAccess() 
    {
        var currentuserId = User.Identity.GetUserId(); // This gets currentloged user id
        var currentuser = await db.Users.SingleOrDefaultAsync(x => x.Id == myUserId); // This gets currentloged user virtual
        return View(await db.Clinics.Were(x => x.clinicuserid == currentuserId).ToListAsync());
    }

Upvotes: 1

Related Questions