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 IQueryableGetAllRoles() { 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 IQueryableGetAll() { 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:
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