Построение XAML-интерфейса на основе ролей ASP.NET или RoleBased UI в XAML

WPF, MVVM, Silverlight | создано: 17.01.2011 | опубликовано: 17.01.2011 | обновлено: 13.01.2024 | просмотров: 7063

Если краткость - сестра таланта, то... Есть ASP.NET сайт, на сайте используются доступ к страница на основе ролей. Есть Silverlight-приложение, которое тоже должно использовать роли ASP.NET сайта, для рисования контента. Вопрос: Как сделать так, чтобы в разметке XAML можно было использовать роли ASP.NET сайта?

Постановка вопроса или очередная хотелка

Вопрос: Как сделать так, чтобы в разметке XAML можно было использовать роли ASP.NET сайта?
Ответ: Очень просто.

Как это будет

Для начала посмотрите на картинки, дабы понять о чем идет речь, и как будет выглядить готовый пример реализации данной "хотелки".


рис 1. Вид Asp.NET стартовой страницы проекта.


рис 2. Так выглядит silverlight-страница, которую открыл неавторизованный пользователь.


рис 3. Так выглядит silverlight-страница, которую открыл авторизованный пользователь с правами "Administrator".


 рис 4. Так выглядит silverlight-страница, которую открыл авторизованный пользовательс правами "Manager".

А вот фрагмент кода XAML, в котором устанавливается видимость того или иного UIElement'а:

<Border
   BorderThickness="1"
   Grid.ColumnSpan="2"
   Grid.Column="1"
   Margin="0,6,0,12"
   Grid.Row="1"
   Visibility="Collapsed">
   <strong><i:Interaction.Behaviors>
      <local:AccessByRoleBehavior
         AllowRoles="Manager"
         UserRoles="{Binding Membership.UserRoles}" />
   </i:Interaction.Behaviors>
</strong>   <TextBlock
      Text="Видимо для роли Manager"
      FontSize="22"
      VerticalAlignment="Center"
      HorizontalAlignment="Center"
      Foreground="White" />
</Border>

Думаю, видно и понятно, что самое главное выделено жирным. Этот AccessByRoleBehavior устанавливает для Border видимость только пользователей у которых есть роль Manager.

Чё те надо, чё те надо...

Итак, для реализации потребуется:

  • ASP.NET сайт с базой данных (сайты без БД бесполезны и без сайта silverlight не сможет работать);
  • Наличие некоторого количества ролей на этом сайте (задаются через утилиту администратора см. рис.6);
  • WCF-сервисы способные выполнять аутентификацию и авторизацию пользователя (всё уже придумано за нас, и более того, уже реализовано, надо просто знать где взять);
  • Класс - обертка для сервисов (так называетмы wrapper) - назову его MembersipService, реализующий интерфейс IMembershipService (как раз в этом классе и кроется сила светлой стороны);
  • Прокси-класс способный быть видимым в XAML-разметке - назову его MembershipServiceProxy, через него будет осуществляться доступ к данным из XAML (важно понять, что через эту обертку из XAML с классом MembershipService может взаимодействовать любой UIElement без использования AccessByRoleBehavior);
  • Класс управляющий поведением UIElement (behavior) - назову его незатейлево - AccessByRoleBehavior (хотя наверное правильнее было бы назвать VisabilityByRoleBehavior);
  • Некоторый MainPage.xaml - для демонстрации кода и возможностей AccessByRoleBehavior (ViewModel для этой страницы, кстати сказать, был тоже создан, но как выяснилось он не потребовался в данной примере.);

Приступим к реализации хотелки

Создаем в Visual Studio новый silverlight-проект. Название Web сайта меняем на "Site" и нажимем [Ок]:

 

рис 5. настраиваем web-хост для нашего silverlight-приложения

Настраивать сайт не буду. Мне кажется это тривиальная задача, но я очень сильно надеюсь, что Вы, уважаемый читатель, уже имеете представление о том, как настраивать сайт на работу с ролями при помощи специальной утилиты управления сайтом:

рис 6. Утилита администратора для настройки сайта.

Если возникнут трудности - пишите, возможно тема "Настройка сайта ASP.NET" может стать следующей в блоге. Теперь у нас есть сайт, на котором есть некоторые зарегистрированные пользователи, а также у этих пользователей заданы роли (см. рис 1.). Пришло время подготовить настроить WCF-службы для аутентификации, авторизации и т.д.

WCF-службы ApplicationServices

Как уже говорилось выше - "всё уже придумано за нас, и более того реализовано". Так вот, Microsoft позаботился о разработчиказ в очередной раз. Всем понятно, что в процессе входа на сайт вряд ли можно придумать что сверхестественное. Всё банально просто. Ввел логин, ввел пароль, если правильно - получит Authenticated Ticket, если нет - твои проблемы. То есть функционал сервисов прост до безоразия. В пространстве имен System.Web.ApplicationServices существуют классы AuthenticationService, RoleService и ProfileService, которые позволяют проверить аутентификацию пользователя, получить его роли на сайте, совершить вход на сайт (с созданием аутентификационного билета и без создания, т.е. проверить валидность введенных данных), выполнить выход с сайта, а также получить список ролей пользователя, данные из профиля и другие полезные вещи. Вот именно эти WCF-службы и надо "прикрутить" к сайту нашего проекта. Расписывать как это делается я не буду, потому что лучше чем статья MSDN это делает, я вряд ли что придумаю лучше, а заниматься обычным "копипастом" нет желания.

Итак, смею предположить, что теперь и WCF-службы аутентификации также успешно подключены. Я подключил все три WCF-службы, хотя в проекте буду использовать только AuthService.svc и RoleService.svc.

Именно эти два сервиса я буду "заворачивать" в класс MembershipService. Поехали дальше...

Обернуть как следует

Класс MembershipService - как раз то самое из-за чего и задумывалась статья. Для того чтобы всё было как надо, требуется добавить референсы на сервисы в Silverlight-приложение:

Не буду приводить весь код класса (вы можете скачать файл проекта и посмотреть), упомину только то что, класс будет реализовывать паттерн Singleton, то есть управление ролями, входом и выходом и т.д. можно будет управлять из "одного места".

#region constructor

private static MembershipService instance;

public static MembershipService Singleton
{
    get
    {
        if (instance == null)
        {
            instance = new MembershipService();
        }
        return instance;

    }
}

private MembershipService()
{
    InitializeService();
}

#endregion

MembershipService реализует интерфейс IMembershipService, который Вы можете реализовать на своё усмотрение. Вот как обозначен этот интерфейс:

/// <summary>
/// Интерфейс представления модуля аутентификации
/// </summary>
public interface IMembershipService
{
    void Login(UserInfo userInfo, Action<bool> result);
    void Logout(Action action);

    bool IsBusy {get;}
    bool IsHasRoles { get; }
    bool IsAuthenticated { get; set; }

    ObservableCollection<string> UserRoles { get; }

    event PropertyChangedEventHandler PropertyChanged;
    event EventHandler<MembershipServiceErrorEventArgs> OnServiceError;
}   

Судя из того, что есть в интерфейсе мы можем:

  • Выполнять вход/выход на сайт, отслеживать занятость сервиса (т.е. если выполняет запрос можно "закрыть" UI при помощи BusyIndicator сделав привязку к свойство IsBusy).
  • Можно проверить есть ли у пользователя вообще какие-нибудь права.
  • Произведен ли вход на сайте.
  • можно получить список ролей текущего пользователя.

MembershipService в XAML? Легко!

Если учесть, что MembershipService класс у нас один на всё приложение (Singleton всё-таки), то именно его нужно использовать для проверки прав пользователя на стороне представления (View) в XAML. Чтобы к этому классу можно было обратиться из XAML-разметки создадим для него прокси-класс:

public class MembershipProxy
{
    public virtual MembershipService Membership
    {
        get { return MembershipService.Singleton; }
    }
}

На этот прокси-класс и будем направлять AccessByRoleBehavior (его создадим дальше). У этого класса два параметра: AllowRoles и UserRoles. Не думаю, что нужно объяснять значения этих параметров, из примера итак видно что к чему:

<i:Interaction.Behaviors>
  <local:AccessByRoleBehavior
     AllowRoles="Manager"
     UserRoles="{Binding Membership.UserRoles}" />
</i:Interaction.Behaviors>

AccessByRoleBehavior класс

В классе есть два основных свойства. Оба этих свойсва являются DependencyProperties чтобы можно было их задавать в разметке:

#region AllowRoles

/// <summary>
/// AllowRoles Dependency Property
/// </summary>
public static readonly DependencyProperty AllowRolesProperty =
    DependencyProperty.Register("AllowRoles", typeof(string), typeof(AccessByRoleBehavior),
        new PropertyMetadata(null));

/// <summary>
/// Gets or sets the AllowRoles property.  This dependency property
/// indicates ....
/// </summary>
public string AllowRoles
{
    get { return (string)GetValue(AllowRolesProperty); }
    set { SetValue(AllowRolesProperty, value); }
}

#endregion

Хочу обратить внимание: свойство называется AllowRoles. То есть подразумевается, что можно вводить множество ролей. Изначально так и было задумано, но впоследствии было решено сделать простой вариант. А вот сделать так, чтобы свойство можно было задавать, например, таким образом: AllowRoles="Admin, User, Buh" - можете считать "домашним заданием". :)

И второе свойство:

#region UserRoles

/// <summary>
/// UserRoles Dependency Property
/// </summary>
public static readonly DependencyProperty UserRolesProperty =
    DependencyProperty.Register("UserRoles", typeof(ObservableCollection<String>), typeof(AccessByRoleBehavior),
        new PropertyMetadata(null,
            new PropertyChangedCallback(OnUserRolesChanged)));

/// <summary>
/// Gets or sets the UserRoles property.  This dependency property
/// indicates ....
/// </summary>
public ObservableCollection<String> UserRoles
{
    get { return (ObservableCollection<String>)GetValue(UserRolesProperty); }
    set { SetValue(UserRolesProperty, value); }
}

/// <summary>
/// Handles changes to the UserRoles property.
/// </summary>
private static void OnUserRolesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((AccessByRoleBehavior)d).CheckVisibility(e);
}

/// <summary>
/// Provides derived classes an opportunity to handle changes to the UserRoles property.
/// </summary>
protected void CheckVisibility(DependencyPropertyChangedEventArgs e)
{
    ObservableCollection<String> collection = (ObservableCollection<String>)e.NewValue;
    string[] roles = new string[collection.Count];
    for (int i = 0; i < collection.Count; i++)
    {
        roles[i] = collection[i];
    }
    SetVisability(roles);
}

#endregion

Примечание: Метод CheckVisibility() специально не использует LINQ, чтобы не включать в проект дополниетльную сборку System.Linq.dll, а вдруг кто-нибудь сможет обойтись без нее во всем приложении (темную сторону силы еще никто не отменял).

Запустим Blend

Если запустить компилляцию, да еще если она завершиться успешно, то открыв файл MainPage.xaml в программе Expression Blend 4 на закладке Assets в категории Behaviors появился новый элемент управления поведением:

Собственно говоря, то к чему были стремления - свершилось! Можно пользовать теперь вновь созданный Behavior. Перетащите его на визуальный контрол (или любой другой UIElement) и установите значения свойств AllowRoles и UserRoles.

Хотелка реализована. Вопросы будут - пишите. (Да прибудет с Вами сила!)