Thursday, December 8, 2011

Custom Role Provider

Custom Role Provider

In the first part of these series we discussed and provided an example of how to create a custom membership provider. We will continue these mini series with a discussion and an example of how to create a custom role provider.
We said in the first part that the reason of why you would like to create a custom membership provider would be if you want to use a data source different than the one supported or if you need to manage role information using a database schema that is different from the database schema used by the providers that ship with the .NET Framework. The reasons of why you would want to create a ciustom role provider are the same.
In the example that I'm going to provide I use Linq-to-SQL data source and my own table structure to keep membership/user and role data.


To implement a custom role provider you need to inherit from RoleProvider abstract class from System.Web.Security namespace. The RoleProvider abstract class inherits the ProviderBase abstract class from the System.Configuration.Provider namespace. As a result, you must implement the required members of the ProviderBase class as well. 
Here's the example:
namespace Custom.Role
{
    using System;
    using System.Linq;
    using System.Configuration;
    using System.Collections.Specialized;
    using System.Configuration.Provider;
    using System.Data;
    using System.Data.SqlClient;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web.Configuration;
    using System.Web.Security;
    using System.Collections.Generic;
    using Custom.CustomData;

    public sealed class CustomRoleProvider : RoleProvider
    {
        private string applicationName;

        public override string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                applicationName = value;
            }
        }
        
        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="usernames"></param>
        /// <param name="roleNames"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (name == null || name.Length == 0)
            {
                name = "CustomRoleProvider";
            }

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Custom Role Provider");
            }

            //Initialize the abstract base class.
            base.Initialize(name, config);

            applicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
        }

        /// <summary>
        /// Add users to roles.
        /// </summary>
        /// <param name="usernames"></param>
        /// <param name="roleNames"></param>
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            try
            {
                using (CustomDataDataContext _db = new CustomDataDataContext())
                {
                    foreach (string username in usernames)
                    {
                        // find each user in users table
                        User user = (from u in _db.Users
                                     where u.UserName == username && u.DeletedOn == null
                                     select u)
                                    .FirstOrDefault();

                        if (user != null)
                        {
                            // find all roles that are contained in the roleNames
                            var AllDbRoles = (from r in _db.Roles select r).ToList();

                            List<int> UserRoles = new List<int>();

                            foreach (var role in AllDbRoles)
                            {
                                foreach (string roleName in roleNames)
                                {
                                    if (role.RoleName == roleName)
                                    {
                                        UserRoles.Add(role.RoleId);
                                        continue;
                                    }
                                }
                            }

                            if (UserRoles.Count > 0)
                            {
                                foreach (var roleId in UserRoles)
                                {
                                    UserInRole UIR = (from uir in _db.UserInRoles
                                                      where uir.UserFK == user.UserId && uir.RoleFK == roleId
                                                      select uir).FirstOrDefault();
                                    if (UIR == null)
                                    {
                                        UIR = new UserInRole();
                                        UIR.UserFK = user.UserId;
                                        UIR.RoleFK = roleId;
                                        UIR.CreatedOn = DateTime.Now;
                                        UIR.DeletedOn = null;
                                        _db.UserInRoles.InsertOnSubmit(UIR);
                                        _db.SubmitChanges();
                                    }
                                    else
                                    {
                                        UIR.DeletedOn = null;
                                        _db.SubmitChanges();
                                    }
                                }
                            }

                        }
                    }
                }
            }
            catch
            {
            }
        }

        /// <summary>
        /// Create new role.
        /// </summary>
        /// <param name="roleName"></param>
        public override void CreateRole(string roleName)
        {
            try
            {
                using (CustomDataDataContext _db = new CustomDataDataContext())
                {
                    Role role = new Role();
                    role.RoleName = roleName;

                    _db.Roles.InsertOnSubmit(role);

                    _db.SubmitChanges();
                }
            }
            catch
            {
            }
        }

        /// <summary>
        /// Delete role.
        /// </summary>
        /// <param name="roleName"></param>
        /// <param name="throwOnPopulatedRole"></param>
        /// <returns>true if role is successfully deleted</returns>
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            bool ret = false;

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    Role role = (from r in _db.Roles
                                 where r.RoleName == roleName
                                 select r).SingleOrDefault();

                    if (role != null)
                    {
                        _db.Roles.DeleteOnSubmit(role);

                        _db.SubmitChanges();

                        ret = true;
                    }
                }
                catch
                {
                    ret = false;
                }
            }

            return ret;
        }

        /// <summary>
        /// Find users in role.
        /// </summary>
        /// <param name="roleName"></param>
        /// <param name="usernameToMatch"></param>
        /// <returns></returns>
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            List<string> users = new List<string>();

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    var usersInRole = from uir in _db.UserInRoles
                                      where uir.Role.RoleName == roleName && uir.User.UserName == usernameToMatch
                                      select uir;

                    if (usersInRole != null)
                    {
                        foreach (var userInRole in usersInRole)
                        {
                            users.Add(userInRole.User.UserName);
                        }
                    }
                }
                catch
                {
                }
            }

            return users.ToArray();
        }

        /// <summary>
        /// Get all roles.
        /// </summary>
        /// <returns></returns>
        public override string[] GetAllRoles()
        {
            List<string> roles = new List<string>();

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    var dbRoles = from r in _db.Roles
                                  select r;

                    foreach (var role in dbRoles)
                    {
                        roles.Add(role.RoleName);
                    }
                }
                catch
                {
                }
            }

            return roles.ToArray();
        }

        /// <summary>
        /// Get all roles for a specific user.
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public override string[] GetRolesForUser(string username)
        {
            List<string> roles = new List<string>();

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    var dbRoles = from r in _db.UserInRoles
                                  where r.User.UserName == username
                                  select r;

                    foreach (var role in dbRoles)
                    {
                        roles.Add(role.Role.RoleName);
                    }

                }
                catch
                {
                }
            }

            return roles.ToArray();
        }

        /// <summary>
        /// Get all users that belong to a role.
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public override string[] GetUsersInRole(string roleName)
        {
            List<string> users = new List<string>();

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    var usersInRole = from uir in _db.UserInRoles
                                      where uir.Role.RoleName == roleName
                                      select uir;

                    if (usersInRole != null)
                    {
                        foreach (var userInRole in usersInRole)
                        {
                            users.Add(userInRole.User.UserName);
                        }
                    }
                }
                catch
                {
                }
            }

            return users.ToArray();
        }

        /// <summary>
        /// Checks if user belongs to a given role.
        /// </summary>
        /// <param name="username"></param>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public override bool IsUserInRole(string username, string roleName)
        {
            bool isValid = false;

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                try
                {
                    var usersInRole = from uir in _db.UserInRoles
                                      where uir.User.UserName == username && uir.Role.RoleName == roleName
                                      select uir;

                    if (usersInRole != null)
                    {
                        isValid = true;
                    }
                }
                catch
                {
                    isValid = false;
                }
            }

            return isValid;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usernames"></param>
        /// <param name="roleNames"></param>
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            try
            {
                using (CustomDataDataContext _db = new CustomDataDataContext())
                {
                    foreach (string username in usernames)
                    {
                        // find each user in users table
                        User user = (from u in _db.Users
                                     where u.UserName == username && u.DeletedOn == null
                                     select u)
                                    .FirstOrDefault();

                        if (user != null)
                        {
                            // find all roles that are contained in the roleNames
                            var AllDbRoles = (from r in _db.Roles select r).ToList();

                            List<int> RemoveRoleIds = new List<int>();

                            foreach (var role in AllDbRoles)
                            {
                                foreach (string roleName in roleNames)
                                {
                                    if (role.RoleName == roleName)
                                    {
                                        RemoveRoleIds.Add(role.RoleId);
                                        continue;
                                    }
                                }
                            }

                            if (RemoveRoleIds.Count > 0)
                            {
                                foreach (var roleId in RemoveRoleIds)
                                {
                                    UserInRole UIR = (from uir in _db.UserInRoles
                                                      where uir.UserFK == user.UserId && uir.RoleFK == roleId
                                                      select uir).FirstOrDefault();
                                    if (UIR != null)
                                    {
                                        UIR.CreatedOn = DateTime.Now;
                                        UIR.DeletedOn = DateTime.Now;
                                        _db.SubmitChanges();
                                    }
                                }
                            }

                        }
                    }
                }
            }
            catch
            {
            }


        }

        /// <summary>
        /// Check if role exists.
        /// </summary>
        /// <param name="configValue"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public override bool RoleExists(string roleName)
        {
            bool isValid = false;

            using (CustomDataDataContext _db = new CustomDataDataContext())
            {
                // check if role exits
                if (_db.Roles.Any(r => r.RoleName == roleName))
                {
                    isValid = true;
                }
            }

            return isValid;
        }

        /// <summary>
        /// Get config value.
        /// </summary>
        /// <param name="configValue"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
            {
                return defaultValue;
            }

            return configValue;
        }
    }
}
After we are finished with the code we need to tell the application that we are going to use a different role provider. We do this by adding a roleProvider tag to web.config under system.web:
    <roleManager enabled="true" defaultProvider="WaooRoleProvider">
      <providers>
        <clear />
        <add name="CustomRoleProvider" type="Custom.Role.CustomRoleProvider" connectionStringName="CustomConnectionString" applicationName="/" />
      </providers>
    </roleManager>
...and that's it. You can use ASP.NET controls like LoginView or Sitemap that work with role providers. These controls are going to work with your provider now. Of course you can always use it in your code, whether it's to create a new role or to check if the user belongs to an existing one:
Roles.RoleExists("SomeRole");
Roles.AddUserToRole("user", "SomeRole");
There you have it. So far we've seen how to provide our own custom user authentication(a custom membership provider), and how to provide role based authentication using our own custom role provider that is using our own data structure.
Next we will see how to extend the information that we have for a user if the default isn't enough for us.
Until then....Happy programming.
Bojan
Other chapters from these series

7 comments:

  1. Hi Bojan,

    i follow your description and save the file in a folder, as you, but i got the error "could not found...". Where is my mistake?

    Thanks, Michael

    ReplyDelete
  2. Is there a way I could acess my custom role provider with razor? before I was doing this:
    if(@User.IsInRole("admin"))
    {
    //code here
    }

    but this method is only for local db (as far as I know)...

    ReplyDelete
  3. Hi Bojar
    Could you add class CustomDataDataContext ?
    I was looking fot it everywhere and have not found..

    ReplyDelete
  4. Hi Bojar,

    same as Tom Jons I am missing the CustomData namespace.
    Nowhere to be found.

    ReplyDelete