I try to avoid magic strings at all costs and I try to always write unit-testable code. The DNN 7 framework UserInfo class relies on magic strings (role names) and is not easily unit tested because its methods are not virtual nor does UserInfo implement an interface. I developed a small reusable framework to overcome both of these DNN UserInfo shortcomings.
The UserInfoAdapter class adapts UserInfo so that unit testing is simplified for methods relying on a UserInfo instance.
- /// <summary>
- /// Adapts the DNN UserInfo class so that methods relying on UserInfo
- /// can be unit-tested.
- /// </summary>
- public class UserInfoAdapter
- {
- private readonly UserInfo _userInfo;
-
- /// <summary>
- /// Construct a new UserInfoAdapter and provide the UserInfo
- /// instance to be adapted.
- /// </summary>
- /// <param name="userInfo">UserInfo instance.</param>
- public UserInfoAdapter(UserInfo userInfo)
- {
- _userInfo = userInfo;
- }
-
- internal UserInfoAdapter()
- : this(null)
- {
- }
-
- /// <summary>
- /// Gets a boolean indicating if the adapted UserInfo instance is null.
- /// </summary>
- public virtual bool IsUserInfoNull { get { return _userInfo == null; } }
-
- /// <summary>
- /// Gets the Roles string array from the adapted UserInfo instance.
- /// </summary>
- public virtual string[] Roles { get { return _userInfo.Roles; } }
-
- /// <summary>
- /// Gets the IsSuperUser boolean value from the adapted UserInfo instance.
- /// </summary>
- public virtual bool IsSuperUser { get { return _userInfo.IsSuperUser; } }
- }
The RoleName base class provides a mechanism for overcoming role name magic strings by encapsulating each DNN role name in one place. Each implementation of RoleName provides a Role value that is identical to a DNN Roles table RoleName value.
- /// <summary>
- /// Provides testable mechanism for retrieving user role information
- /// along with tight database role integration.
- /// </summary>
- public abstract class RoleName
- {
- /// <summary>
- /// Gets the role associated with the RoleName instance.
- /// Returned value should match exactly to a DNN database Roles.RoleName value.
- /// </summary>
- public abstract string Role { get; }
-
- /// <summary>
- /// Gets false to indicate that this is not a SuperUser role.
- /// </summary>
- public virtual bool IsSuperUserRole { get { return false; } }
- }
Here are sample RoleName implementations for the Admin role and SuperUser status.
- /// <summary>
- /// SuperUser role.
- /// </summary>
- public class SuperUserRole : RoleName
- {
- /// <summary>
- /// Gets a value indicating a SuperUser role. This is not a
- /// DNN Roles.RoleName value.
- /// </summary>
- public override string Role { get { return "SuperUser"; } }
-
- /// <summary>
- /// Gets true to indicate that this is a SuperUser role.
- /// </summary>
- public override bool IsSuperUserRole { get { return true; } }
- }
-
- /// <summary>
- /// Administrators role.
- /// </summary>
- public class AdminRole : RoleName
- {
- /// <summary>
- /// Gets the DNN database Roles.RoleName value for an Administrator user.
- /// </summary>
- public override string Role { get { return "Administrators"; } }
- }
RoleNames can be configured to work like enumerators by declaring them as static instances:
- public static class Role
- {
- private static RoleName _superUser = new SuperUserRole();
- public static RoleName SuperUser { get { return _superUser; } }
-
- private static RoleName _admin = new AdminRole();
- public static RoleName Admin { get { return _admin; } }
-
- private static RoleName _regUser = new RegisteredUserRole();
- public static RoleName RegisteredUser { get { return _regUser; } }
-
- private static RoleName _subscriber = new SubscriberRole();
- public static RoleName Subscriber { get { return _subscriber; } }
- }
With these classes now in place, it is easy to write an IsInRole extension method override for the DNN UserInfo class. I also created a RoleComparer implementation of IEqualityComparer<string> to ensure role equality is case-insensitive.
- public static class UserInfoUtil
- {
- /// <summary>
- /// Returns a boolean value indicating whether or not the User
- /// is in one of the supplied roles.
- /// </summary>
- /// <param name="user">A UserInfo instance.</param>
- /// <param name="roles">One or more roles.</param>
- /// <returns>true if the User is in one or more of the supplied roles;
- /// otherwise returns false.</returns>
- public static bool IsInRole(this UserInfo user, params RoleName[] roles)
- {
- return IsInRole(new UserInfoAdapter(user), roles,
- IsSuperUserRole, IsNonSuperUserRole);
- }
-
- internal static bool IsInRole(UserInfoAdapter aUser, RoleName[] roles,
- Func<UserInfoAdapter, RoleName[], bool> isSuperUserRole,
- Func<UserInfoAdapter, RoleName[], bool> isNonSuperUserRole)
- {
- if (aUser.IsUserInfoNull) return false;
- bool isInRole = isSuperUserRole(aUser, roles);
- if (!isInRole)
- isInRole = isNonSuperUserRole(aUser, roles);
- return isInRole;
- }
-
- internal static bool IsSuperUserRole(UserInfoAdapter aUser, RoleName[] roles)
- {
- RoleName supervisor = roles.FirstOrDefault(r => r.IsSuperUserRole == true);
- if (supervisor != null)
- {
- return aUser.IsSuperUser;
- }
- else return false;
- }
-
- internal static bool IsNonSuperUserRole(UserInfoAdapter aUser, RoleName[] roles)
- {
- bool isInRole = false;
- string[] userRoles = aUser.Roles;
- int i = 0;
- while (i < roles.Length && !isInRole)
- {
- isInRole = userRoles.Contains(roles[i].Role, new RoleComparer());
- i++;
- }
- return isInRole;
- }
- }
-
- /// <summary>
- /// Compares two roles for equality.
- /// </summary>
- internal class RoleComparer : IEqualityComparer<string>
- {
- /// <summary>
- /// Returns a boolean value indicating equality between
- /// two roles.
- /// </summary>
- /// <param name="roleOne">First role.</param>
- /// <param name="roleTwo">Second role.</param>
- /// <returns>true if the two roles are equal using a case-insensitive
- /// test; otherwise returns false.</returns>
- public bool Equals(string roleOne, string roleTwo)
- {
- return (string.Compare(roleOne, roleTwo, true) == 0);
- }
-
- /// <summary>
- /// Returns a hash code for the role.
- /// </summary>
- /// <param name="role">A user role.</param>
- /// <returns>The hash code of the role string.</returns>
- public int GetHashCode(string role)
- {
- return role.GetHashCode();
- }
- }
Here are the unit tests for the UserInfoUtil methods. These tests use the NUnit and Rhino.Mocks frameworks.
- [TestFixture]
- public class UserInfoUtilTests
- {
- #region Variables and Constants
- private UserInfoAdapter _stubUserInfo;
- private RoleName _suRole;
- private RoleName _adminRole;
- private RoleName _otherRole;
- private Func<UserInfoAdapter, RoleName[], bool> _stubIsSuperUserRole;
- private Func<UserInfoAdapter, RoleName[], bool> _stubIsNonSuperUserRole;
- #endregion
-
-
- #region Setup
-
- [TestFixtureSetUp]
- public void InitFixture()
- {
- _suRole = new TestSuperUserRole();
- _adminRole = new TestAdminRole();
- _otherRole = new TestOtherRole();
- }
-
- [SetUp]
- public void InitTest()
- {
- _stubUserInfo = MockRepository.GenerateStub<UserInfoAdapter>();
- _stubUserInfo.Stub(ui => ui.IsUserInfoNull).Return(false);
-
- _stubIsSuperUserRole = MockRepository.GenerateStub<Func<UserInfoAdapter, RoleName[], bool>>();
- _stubIsNonSuperUserRole = MockRepository.GenerateStub<Func<UserInfoAdapter, RoleName[], bool>>();
- }
-
- #endregion
-
-
- #region Unit Tests
-
- [Test]
- public void IsInRole_IsUserInfoNull_True_Returns_False()
- {
- var stubUserInfo = MockRepository.GenerateStub<UserInfoAdapter>();
- stubUserInfo.Stub(ui => ui.IsUserInfoNull).Return(true);
- Assert.That(UserInfoUtil.IsInRole(stubUserInfo, null, _stubIsSuperUserRole, _stubIsNonSuperUserRole),
- Is.EqualTo(false));
- }
-
- [Test]
- public void IsInRole_IsUserInfoNull_False_IsInSuperUserRole_True_Returns_True()
- {
- _stubIsSuperUserRole.Stub(f => f(Arg<UserInfoAdapter>.Is.Equal(_stubUserInfo), Arg<RoleName[]>.Is.Anything)).Return(true);
- Assert.That(UserInfoUtil.IsInRole(_stubUserInfo, null, _stubIsSuperUserRole, _stubIsNonSuperUserRole),
- Is.EqualTo(true));
- }
-
- [TestCase(true)]
- [TestCase(false)]
- public void IsInRole_IsUserInfoNull_False_IsInSuperUserRole_False_Returns_IsNonSuperUserRole_Value(bool isNonSuperUserRole)
- {
- _stubIsSuperUserRole.Stub(f => f(Arg<UserInfoAdapter>.Is.Equal(_stubUserInfo), Arg<RoleName[]>.Is.Anything)).Return(false);
- _stubIsNonSuperUserRole.Stub(f => f(Arg<UserInfoAdapter>.Is.Equal(_stubUserInfo), Arg<RoleName[]>.Is.Anything)).Return(isNonSuperUserRole);
- Assert.That(UserInfoUtil.IsInRole(_stubUserInfo, null, _stubIsSuperUserRole, _stubIsNonSuperUserRole),
- Is.EqualTo(isNonSuperUserRole));
- }
-
- [Test]
- public void IsSuperUserRole_SuperUserRole_Not_Provided_Returns_False()
- {
- Assert.That(UserInfoUtil.IsSuperUserRole(_stubUserInfo, new RoleName[] { _adminRole, _otherRole }),
- Is.EqualTo(false));
- }
-
- [TestCase(true)]
- [TestCase(false)]
- public void IsSuperUserRole_SuperUserRole_Provided_Returns_IsSuperUser_Value_From_User(bool isSuperUser)
- {
- _stubUserInfo.Stub(ui => ui.IsSuperUser).Return(isSuperUser);
- Assert.That(UserInfoUtil.IsSuperUserRole(_stubUserInfo, new RoleName[] { _suRole }),
- Is.EqualTo(isSuperUser));
- Assert.That(UserInfoUtil.IsSuperUserRole(_stubUserInfo, new RoleName[] { _suRole, _adminRole, _otherRole }),
- Is.EqualTo(isSuperUser));
- }
-
- [Test]
- public void IsNonSuperUserRole_User_Not_In_Provided_Roles_Returns_False()
- {
- _stubUserInfo.Stub(ui => ui.Roles).Return(new string[] { "X", "Y" });
- Assert.That(UserInfoUtil.IsNonSuperUserRole(_stubUserInfo, new RoleName[] { _adminRole, _otherRole }),
- Is.EqualTo(false));
- }
-
- [Test]
- public void IsNonSuperUserRole_User_In_Provided_Roles_Regardless_Of_Case_Returns_True()
- {
- _stubUserInfo.Stub(ui => ui.Roles).Return(new string[] { "A", "o" });
- Assert.That(UserInfoUtil.IsNonSuperUserRole(_stubUserInfo, new RoleName[] { _adminRole, _otherRole }),
- Is.EqualTo(true));
- Assert.That(UserInfoUtil.IsNonSuperUserRole(_stubUserInfo, new RoleName[] { _adminRole }),
- Is.EqualTo(true));
- Assert.That(UserInfoUtil.IsNonSuperUserRole(_stubUserInfo, new RoleName[] { _otherRole }),
- Is.EqualTo(true));
- }
-
- #endregion
-
- public class TestSuperUserRole : RoleName
- {
- public override string Role { get { return "SU"; } }
- public override bool IsSuperUserRole { get { return true; } }
- }
-
- public class TestAdminRole : RoleName
- {
- public override string Role { get { return "A"; } }
- }
-
- public class TestOtherRole : RoleName
- {
- public override string Role { get { return "O"; } }
- }
- }
Here is an example of how to implement. This method returns true if the current DNN user is either a SuperUser or an Administrator.
- public bool HasAdminRole()
- {
- return DotNetNuke.Entities.Users.UserController.GetCurrentUserInfo().IsInRole(Role.SuperUser, Role.Admin);
- }