niedziela, 8 kwietnia 2012

Custom RoleProvider ASP.NET

Zabrałem się za projekt w ASP.NET MVC 3. W planach jest napisanie nowego, rewolucyjnego systemu :) można więc spodziewać się wpisów związanych z tą technologią.
Problem: w projekcie będzie możliwość zakładania kont użytkowników. Będzie także podział na role. Początkowo myślałem o wykorzystaniu gotowego rozwiązania (ASP.NET providers), jednak nie do końca mi się ono podoba. MembershipProvider i RoleProvider to świetne mechanizmy, ale tworzą w bazie standardowo niepotrzebny narzut zbędnych kolumn. Dlatego wziąłem się za pisanie ich pod mój system od początku.
O tworzeniu MembershipProvidera pisałem już we wcześniejszym poście, w tym trochę porad jak napisać RoleProvidera.

Sprawa jest łatwiejsza niż w przypadku MembershipProvidera, ze względu na to iż ilość metod które należy zaimplementować jest dużo mniejsza.

Startując od początku:

W bazie potrzebujemy odpowiednich tabel do przetrzymywania danych o użytkownikach i ich rolach:


Standardowa realizacja wiele do wiele (użytkownik może być przypisany do więcej niż jednej roli).

Wystartujemy od szablonu:

    public class MyRoleProvider : RoleProvider
    {
        public override bool IsUserInRole(string username, string roleName)
        {
            throw new NotImplementedException();
        }

        public override string[] GetRolesForUser(string username)
        {
            throw new NotImplementedException();
        }

        public override void CreateRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            throw new NotImplementedException();
        }

        public override bool RoleExists(string roleName)
        {
            throw new NotImplementedException();
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override string[] GetUsersInRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            throw new NotImplementedException();
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string ApplicationName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }
    }

Zaczniemy od potrzebnych repozytoriów:

      using System.Linq;
using BusinessObjects.Entities;
using DataAccessLayer.Repositories.Contract;

namespace DataAccessLayer.Repositories.Implementation
{
    public class RoleRepository : BaseRepository, IRoleRepository
    {
        public void AddRole(Role role)
        {
            AddRole(role.Rolename);
        }

        public void AddRole(string roleName)
        {
            ChemistContext.Roles.Add(new Role{Rolename = roleName});
        }

        public IQueryable GetAllRoles()
        {
            return ChemistContext.Roles.AsQueryable();
        }

        public Role GetRoleByName(string roleName)
        {
            return ChemistContext.Roles.SingleOrDefault(x => x.Rolename == roleName);
        }

        public Role GetRoleById(int id)
        {
            return ChemistContext.Roles.SingleOrDefault(x => x.Id == id);
        }

        public IQueryable GetRolesForUser(string userName)
        {
            var userRoles = from user in ChemistContext.Users
                            join userInRoles in ChemistContext.UserInRoles on user.Id equals userInRoles.UserId
                            join roles in ChemistContext.Roles on userInRoles.RoleId equals roles.Id
                            where user.Login == userName
                            select roles;

            return userRoles;
        }

        public void DeleteRole(string roleName)
        {
            var role = ChemistContext.Roles.SingleOrDefault(x => x.Rolename == roleName);
            if (role != null)
            {
                ChemistContext.Roles.Remove(role);
                ChemistContext.SaveChanges();
            }
        }
    }
}

using System.Linq;
using BusinessObjects.Entities;
using DataAccessLayer.Repositories.Contract;

namespace DataAccessLayer.Repositories.Implementation
{
    public class UserInRoleRepository : BaseRepository, IUserInRoleRepository
    {
        public void AddUserToRole(UserInRole userInRole)
        {
            AddUserToRole(userInRole.UserId, userInRole.RoleId);
        }

        public void AddUserToRole(int idUser, int idRole)
        {
            ChemistContext.UserInRoles.Add(new UserInRole{UserId = idUser, RoleId = idRole});
            ChemistContext.SaveChanges();
        }

        public void AddUserToRole(string userName, string roleName)
        {
            var idUser = ChemistContext.Users.Single(x => x.Login == userName).Id;
            var idRole = ChemistContext.Roles.Single(x => x.Rolename == roleName).Id;
            AddUserToRole(idUser, idRole);
        }

        public IQueryable GetAll()
        {
            return ChemistContext.UserInRoles.AsQueryable();
        }

        public UserInRole GetByIdRoleIdUser(int idRole, int idUser)
        {
            return ChemistContext.UserInRoles.SingleOrDefault(x => x.RoleId == idRole && x.UserId == idUser);
        }

        public UserInRole GetByUserNameRoleName(string userName, string roleName)
        {
            var userInRole = from users in ChemistContext.Users
                             join userInRoles in ChemistContext.UserInRoles on users.Id equals userInRoles.UserId
                             join roles in ChemistContext.Roles on userInRoles.RoleId equals roles.Id
                             where users.Login == userName && roles.Rolename == roleName
                             select userInRoles;

            return userInRole.SingleOrDefault();
        }

        public void DeleteRoleAndUsers(string roleName)
        {
            var userRolesToDelete = from roles in ChemistContext.Roles
                                    join userInRoles in ChemistContext.UserInRoles on roles.Id equals userInRoles.RoleId
                                    where roles.Rolename == roleName
                                    select userInRoles;
            foreach (var userInRole in userRolesToDelete)
            {
                ChemistContext.UserInRoles.Remove(userInRole);
            }

            ChemistContext.SaveChanges();
        }

        public void DeleteUserFromRole(string userName, string roleName)
        {
            var usersInRole = from users in ChemistContext.Users
                              join userInRoles in ChemistContext.UserInRoles on users.Id equals userInRoles.UserId
                              join roles in ChemistContext.Roles on userInRoles.RoleId equals roles.Id
                              where roles.Rolename == roleName && users.Login == userName
                              select userInRoles;

            foreach (var user in usersInRole)
            {
                ChemistContext.UserInRoles.Remove(user);
            }

            ChemistContext.SaveChanges();
        }

        public string[] GetByRoleName(string roleName)
        {
            var usersInRole = from users in ChemistContext.Users
                              join userInRoles in ChemistContext.UserInRoles on users.Id equals userInRoles.RoleId
                              join roles in ChemistContext.Roles on userInRoles.RoleId equals  roles.Id
                              where roles.Rolename == roleName
                              select users.Login;

            return usersInRole.ToArray();
        }

        public string[] GetByRoleNameContains(string roleName)
        {
            var usersInRole = from users in ChemistContext.Users
                              join userInRoles in ChemistContext.UserInRoles on users.Id equals userInRoles.RoleId
                              join roles in ChemistContext.Roles on userInRoles.RoleId equals roles.Id
                              where roles.Rolename.Contains(roleName)
                              select users.Login;

            return usersInRole.ToArray();
        }
    }
}

Nie pozostaje nic innego jak zaimplementowanie samego RoleProvidera:

using System;
using System.Configuration.Provider;
using System.Transactions;
using System.Web.Security;
using BusinessObjects.Enums;
using DataAccessLayer.Repositories.Contract;
using DataAccessLayer.Repositories.Implementation;
using System.Linq;
using Services.Contract;
using Services.Implementation;

namespace Chemist.Infrastructure
{
    public class ChemistRoleProvider : RoleProvider
    {
        private readonly IRoleRepository roleRepository;
        private readonly IUserInRoleRepository userInRoleRepository;
        private readonly IExceptionService exceptionService = new ExceptionService(new EventLogRepository());
        private string applicationName = "Chemistry";

        public ChemistRoleProvider()
        {
            roleRepository = new RoleRepository();
            userInRoleRepository = new UserInRoleRepository();
        }

        public override bool IsUserInRole(string userName, string roleName)
        {
            var isUserInRole = userInRoleRepository.GetByUserNameRoleName(userName, roleName) != null;

            return isUserInRole;
        }

        public override string[] GetRolesForUser(string userName)
        {
            return roleRepository.GetRolesForUser(userName).Select(x => x.Rolename).ToArray();
        }

        public override void CreateRole(string roleName)
        {
            roleRepository.AddRole(roleName);
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (!RoleExists(roleName))
            {
                throw new ProviderException("Role does not exist.");
            }
            if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
            {
                throw new ProviderException("Cannot delete a populated role.");
            }

            try
            {
                using (
                    var transaction = new TransactionScope(TransactionScopeOption.Required,
                                                           new TransactionOptions
                                                           {IsolationLevel = IsolationLevel.ReadCommitted}))
                {
                    userInRoleRepository.DeleteRoleAndUsers(roleName);
                    roleRepository.DeleteRole(roleName);
                    transaction.Complete();
                    return true;
                }
            }
            catch (Exception ex)
            {
                exceptionService.Write(ex, EventSource.RoleProvider);
                return false;
            }
        }

        public override bool RoleExists(string roleName)
        {
            var roleExist = roleRepository.GetRoleByName(roleName);

            return roleExist != null;
        }

        public override void AddUsersToRoles(string[] userNames, string[] roleNames)
        {
            foreach (var rolename in roleNames)
            {
                if (!RoleExists(rolename))
                {
                    throw new ProviderException("Role name not found.");
                }
            }

            foreach (var username in userNames)
            {
                if (username.Contains(","))
                {
                    throw new ArgumentException("User names cannot contain commas.");
                }

                foreach (var rolename in roleNames)
                {
                    if (IsUserInRole(username, rolename))
                    {
                        throw new ProviderException("User is already in role.");
                    }
                }
            }

            using (var transaction = new TransactionScope())
            {
                foreach (var username in userNames)
                {
                    foreach (var rolename in roleNames)
                    {
                        userInRoleRepository.AddUserToRole(username, rolename);
                    }
                }

                transaction.Complete();
            }
        }

        public override void RemoveUsersFromRoles(string[] userNames, string[] roleNames)
        {
            foreach (var rolename in roleNames)
            {
                if (!RoleExists(rolename))
                {
                    throw new ProviderException("Role name not found.");
                }
            }

            foreach (var username in userNames)
            {
                foreach (var rolename in roleNames)
                {
                    if (!IsUserInRole(username, rolename))
                    {
                        throw new ProviderException("User is not in role.");
                    }
                }
            }

            foreach (string username in userNames)
            {
                foreach (string rolename in roleNames)
                {
                    userInRoleRepository.DeleteUserFromRole(username, rolename);
                }
            }
        }

        public override string[] GetUsersInRole(string roleName)
        {
            return userInRoleRepository.GetByRoleName(roleName);
        }

        public override string[] GetAllRoles()
        {
            return roleRepository.GetAllRoles().Select(x => x.Rolename).ToArray();
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            return userInRoleRepository.GetByRoleNameContains(roleName);
        }

        public override string ApplicationName
        {
            get { return applicationName; }
            set { applicationName = value; }
        }
    }
}

Ostatnim krokiem, który należy wykonać, to dodanie w Web.configu informacji o tym, aby został użyty nasz RoleProvider:


Code:
    <roleManager enabled="true" defaultProvider="ChemistRoleProvider">
      <providers>
        <add name="ChemistRoleProvider" type="Chemist.Infrastructure.ChemistRoleProvider"/>
      </providers>
    </roleManager>



Ilość kodu może wydać się duża, jednak raz napisany RoleProvider jest przeważnie wykorzystywany w wielu późniejszych projektach.

Brak komentarzy:

Prześlij komentarz