Реализация поставщика Roles API

89

Исходный код пользовательского поставщика - XmlRoleProvider

Реализация поставщика ролей намного проще, чем поставщика членства, поскольку для управления ролями применяются намного более простые структуры. Реализация поставщика ролей не привносит никаких новых концепций. Она просто требует вызова соответствующих методов ранее представленного класса RoleStore для создания ролей, назначения пользователей на роли, а также удаления пользователей из ролей. Полный интерфейс поставщика ролей выглядит следующим образом:

using System;
using System.IO;
using System.Web;
using System.Web.Security;
using System.Configuration.Provider;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Permissions;
using Professorweb.Providers.Store;

namespace Professorweb.Providers
{
    public class XmlRoleProvider : RoleProvider
    {
        private string _FileName;
        private string _ApplicationName;
        private RoleStore _CurrentStore = null;

        public override void Initialize(string name, NameValueCollection config)

        private RoleStore CurrentStore
        {
            get;
        }
        public override string ApplicationName
        {
            get;
            set;
        }
        public override void CreateRole(string roleName)
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        public override bool RoleExists(string roleName)
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        public override string[] GetAllRoles()
        public override string[] GetRolesForUser(string username)
        public override string[] GetUsersInRole(string roleName)
        public override bool IsUserInRole(string username, string roleName)
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    }
}

Как видите, класс XmlRoleProvider унаследован от базового класса RoleProvider. Опять-таки, здесь переопределяется метод Initialize() для инициализации дополнительных свойств. Но на этот раз инициализация поставщика намного проще, потому что поставщик ролей поддерживает лишь небольшое количество свойств. Единственное свойство, предоставленное базовым классом - ApplicationName. Все остальные - на ваше усмотрение. Поэтому инициализация достаточно проста:

public override void Initialize(string name, NameValueCollection config)
{
    if (config == null)
    {
        throw new ArgumentNullException("config");
    }
    if (string.IsNullOrEmpty(name))
    {
        name = "XmlRoleProvider";
    }
    if (string.IsNullOrEmpty(config["description"]))
    {
        config.Remove("description");
        config.Add("description", "XML Role Provider");
    }

    // Инициализация базового класса
    base.Initialize(name, config);

    // Инициализация свойств
    _ApplicationName = "DefaultApp";
    foreach (string key in config.Keys)
    {
        if (key.ToLower().Equals("applicationname"))
            ApplicationName = config[key];
        else if (key.ToLower().Equals("filename"))
            _FileName = config[key];
    }
}

Процедура инициализации проверяет имя и описание параметров конфигурации, после чего инициализирует их значениями по умолчанию, если они еще не сконфигурированы. Затем она вызывает реализацию Initialize() базового класса. Не забывайте вызывать метод Initialize() базового класса; в противном случае значения конфигурации по умолчанию не будут инициализированы. Далее она инициализирует свойства, в то время как реализация XmlRoleProvider знает только об установках ApplicationName и FileName. Опять-таки, FileName указывает имя XML-файла, в котором сохраняется информация о роли.

Этот класс поддерживает несколько методов для управления ролями: CreateRole(), DeleteRole() и RoleExists.() Внутри этих методов необходимо обращаться к методам RoleStore, как показано в следующем примере CreateRole:

public override void CreateRole(string roleName)
{
    try
    {
        SimpleRole NewRole = new SimpleRole();
        NewRole.RoleName = roleName;
        NewRole.AssignedUsers = new StringCollection();

        CurrentStore.Roles.Add(NewRole);
        CurrentStore.Save();
    }
    catch
    {
        // Если при сохранении хранилища или при сериализации содержимого 
        // возникает исключение, просто передать его вызывающему коду. 
        // Было бы яснее, если бы работа здесь производилась со специальными 
        // классами исключений, с передачей вызывающему коду детализированной 
        // информации, но для простоты пусть все остается в таком виде
        throw;
    }
}

По сравнению с представленным в предыдущей статье методом CreateUser() этот код намного проще. Он создает новый экземпляр SimpleRole и затем добавляет новую роль в лежащее в основе хранилище RoleStore. И снова удобно добавить свойство CurrentRole к реализации поставщика членства. Оно предоставит легкий доступ к лежащему в основе хранилищу:

private RoleStore CurrentStore
{
    get
    {
        if (_CurrentStore == null)
            _CurrentStore = RoleStore.GetStore(_FileName);
        return _CurrentStore;
    }
}

Метод RoleExists() проходит по списку CurrentStore.Roles и проверяет наличие в нем роли по имени, переданном в параметре. Метод DeleteRole() пытается найти роль в списке ролей хранилища, и если она там есть, он удаляет роль из хранилища и сохраняет хранилище обратно в файловую систему вызовом CurrentStore.Save(). Большинство методов пользовательского поставщика ролей столь же просты. Наиболее сложными операциями являются добавление пользователя к роли и удаление его из роли. Ниже показан первый метод - добавление пользователя к роли:

public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
    try
    {
        // Получить роли, подлежащие модификации
        foreach (string roleName in roleNames)
        {
            SimpleRole Role = CurrentStore.GetRole(roleName);
            if (Role != null)
            {
        foreach (string userName in usernames)
        {
            if (!Role.AssignedUsers.Contains(userName))
            {
                Role.AssignedUsers.Add(userName);
            }
        }
            }
        }

        CurrentStore.Save();
    }
    catch
    {
        throw;
    }
}

Хотя класс Roles предлагает больше перегрузок метода этого типа, ваш поставщик должен реализовать наиболее гибкую из них: добавление всех пользователей, указанных в первом параметре-массиве, ко всем ролям, переданным во втором параметре-массиве. Таким образом, необходимо пройти по списку поддерживаемых ролей, сохраненных в XML-файле, и для каждой из ролей, переданных в параметре roleNames, добавить всех пользователей из параметра usernames. Именно это делает данный метод. Внутри первого цикла foreach осуществляется проход по массиву имен ролей. Роль извлекается из хранилища вызовом метода GetRole() класса RoleStore, и затем к ней добавляются все пользователи, указанные в параметре usernames. В конце вызывается CurrentStore.Save() для сериализации ролей обратно в XML-файл.

Метод RemoveUsersFromRoles() выполняет обратную операцию:

public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
    try
    {
        // Получить роли, подлежащие модификации
        List<SimpleRole> TargetRoles = new List<SimpleRole>();
        foreach (string roleName in roleNames)
        {
            SimpleRole Role = CurrentStore.GetRole(roleName);
            if (Role != null)
            {
                foreach (string userName in usernames)
                {
                    if (Role.AssignedUsers.Contains(userName))
                    {
                        Role.AssignedUsers.Remove(userName);
                    }
                }
            }
        }

        CurrentStore.Save();
    }
    catch
    {
        throw;
    }
}

Единственное отличие этого метода от представленного выше состоит в том, что он удаляет пользователей, указанных в параметре usernames, из всех ролей, переданных в параметре roleNames. Остальная логика метода точно такая же. Оставшиеся методы пользовательского поставщика ролей реализовать легко: в большинстве случаев они просто выполняют итерацию по ролям, находящимся в хранилище, и возвращают некоторую информацию - как правило, массивы строк с именами пользователей или именами ролей:

public override string[] GetRolesForUser(string username)
{
    try
    {
        List<SimpleRole> RolesForUser = CurrentStore.GetRolesForUser(username);
        string[] Results = new string[RolesForUser.Count];
        for (int i = 0; i < Results.Length; i++)
            Results[i] = RolesForUser[i].RoleName;
        return Results;
    }
    catch
    {
        throw;
    }
}

public override string[] GetUsersInRole(string roleName)
{
    try
    {
        return CurrentStore.GetUsersInRole(roleName);
    }
    catch
    {
        throw;
    }
}

public override bool IsUserInRole(string username, string roleName)
{
    try
    {
        SimpleRole Role = CurrentStore.GetRole(roleName);
        if (Role != null)
        {
            return Role.AssignedUsers.Contains(username);
        }
        else
        {
            throw new ProviderException("Роль не найдена!");
        }
    }
    catch
    {
        throw;
    }
}

Первый метод возвращает все роли одного пользователя. Для этого он вызывает метод GetRolesForUser() класса RoleStore, который возвращает список объектов класса SimpleRole. Затем результат отображается на массив строк и возвращается вызывающему коду. Извлечение пользователей для одной роли еще проще, поскольку эта функциональность предоставлена классом RoleStore. И, наконец, IsUserInRole проверяет, назначен ли пользователь на роль, извлекая роль и затем вызывая метод Contains() класса StringCollection для проверки того, присутствует ли пользователь в коллекции AssignedUsers объекта SimpleRole.

Следует взглянуть на еще один метод - FindUsersInRoles:

public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
    try
    {
        List<string> Results = new List<string>();
        Regex Expression = new Regex(usernameToMatch.Replace("%", @"\w*"));
        SimpleRole Role = CurrentStore.GetRole(roleName);
        if (Role != null)
        {
            foreach (string userName in Role.AssignedUsers)
            {
                if (Expression.IsMatch(userName))
                Results.Add(userName);
            }
        }
        else
        {
            throw new ProviderException("Role does not exist!");
        }

        return Results.ToArray();
    }
    catch
    {
        throw;
    }
}

Этот метод пытается найти пользователей на основе соответствия шаблону, переданному в параметре roleName. Для этой цели он извлекает роль из хранилища и затем создает регулярное выражение. Символ % используется поставщиком членства SQL для сравнения с шаблоном, а поскольку иметь поставщика, совместимого с существующими реализациями - всегда хорошая идея, он также используется для нахождения шаблонного соответствия в своем поставщике.

Однако регулярные выражения не воспринимают символ % как место подстановки любого символа строки, поэтому его понадобится заменить представлением, которое регулярные выражения понимают - \w*. Теперь, когда класс Membership получит этот символ в качестве места подстановки, функция сравнения с шаблоном будет работать, и она станет совместимой с реализацией SqlMembershipProvider (которая использует %).

Остальной код функции осуществляет проход по пользователям, назначенным на роль, и если имя пользователя соответствует шаблону, он добавляется в результирующий список строк, который будет возвращен в виде простого строкового массива.

Как видите, реализация пользовательского поставщика ролей проста, если ранее был реализован пользовательский поставщик членства. Этот процесс не требует освоения каких-то новых концепций. В общем, зная, как реализовать один поставщик, несложно реализовать другой. Поэтому не составит особого труда реализовать пользовательские поставщики профилей и персонализации. Теперь самое время посмотреть, как использовать эти поставщики.

Пройди тесты
Лучший чат для C# программистов