Построение 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.
Хотелка реализована. Вопросы будут - пишите. (Да прибудет с Вами сила!)