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.