Иерархические шаблоны данных в Silverlight
ОГЛАВЛЕНИЕ
Данная статья изучает некоторые свойства иерархических шаблонов данных с помощью пробного приложения "Пользователи и группы". В примере используются управляющие элементы DataGrid(сетка данных) и TreeView(список с древовидным отображением) Silverlight вместе с преобразователями значения.
• Скачать исходный код - 332 Кб
Введение
Иерархические шаблоны данных являются мощным способом организации и привязки данных, имеющимся в основе представления Windows (WPF) и Silverlight. Прочитав данную статью и пример кода, вы получите глубокие знания о том, как работают иерархические шаблоны данных, как работает привязка данных в Silverlight, и о некоторых изобретательных способах их применения в приложениях, а также как вносить ряд ориентированных на производительность изменений в приложение, чтобы обеспечить отложенную загрузку больших наборов данных.
Подготовка
Есть много бизнес-требований, предполагающих отображение данных в иерархическом формате. Обычно конструкция пользовательского интерфейса интернета требовала управления иерархией посредством кода и рекурсивного выполнения итераций для построения дерева. Иерархические наборы данных отсутствовали.
Иерархический шаблон данных позволяет привязать данные, являющиеся иерархическими и автореферентными. Шаблон понимает, как рекурсивно обойти дерево, и обеспечивает более четкое разделение реализации данных и представления. Понимание принципа работы иерархического шаблона данных позволяет выгодно использовать преимущества производительности, путем загрузки данных в нужное время и только когда они нужны для отображения.
В данном примере берется типичная схема: пользователи в организационных группах. Группы формируют иерархию объектов "группы": например, Северная Америка может иметь группу для Восточного региона, и внутри нее группу для администраторов, и так далее. Пользователь, принадлежащий любой группе, автоматически принадлежит родителям группы, и родителям другой группы, и так далее.
Уникальность этой схемы заключается в том, что группа может иметь два типа потомков: другая группа или пользователь. Как можно представить два разных типа данных в иерархической структуре данных и выгодно использовать шаблон в приложении? Более того, что если человек, использующий приложение, хочет добраться до конкретной группы и изучить пользователь в ней, не дожидаясь загрузки всей организации? Представьте наличие 10.000 пользователей в системе и ожидание загрузки всех этих деталей в сравнении с возможностью просто загрузить пользователей, отображаемых в конкретном уровне иерархии.
Начало работы: домен и транспорты
При построении сервис-ориентированного кода предоставляются классы транспорта для сущностей домена. Сущность домена может быть сложным классом, содержащим несколько подклассов и большой граф объекта. Например, в принципе, можно представить наличие класса User с именем пользователя, адресом электронной почты, именем, фамилией, номером социального страхования, несколькими адресами, веб-адресами, личными блогами, логинами твиттера и многими другими точками данных, не нужных при представлении сводных данных. Отправка всей этой информации приложению Silverlight была бы растратой пропускной способности сети и памяти браузера.
Поэтому создаются объекты транспорта, содержащие более маленькие, "развернутые" версии объектов. Чтобы облегчить преобразование объекта домена в объект транспорта, предоставляется конструктор такого вида:
...
public UserTransport(UserEntity user)
{
ID = user.ID;
Username = user.Username;
}
...
Для упрощения примера не создаются классы и архитектура службы на стороне веб-приложения, зато создается "тестовая база данных" внутри приложения Silverlight. Это упрощает настройку и запуск. Были зашиты некоторые инструкции Debug, чтобы можно было увидеть, как вызываются определенные службы.
Чтобы узнать больше об абстрагировании вызовов служб, жмите сюда. По сути, будет имитироваться "объект-помощник", вызываемый для инициации вызова службы.
Транспорты
В примере есть два транспорта: группа и пользователь. Посмотрим на эти классы:
public class UserTransport
{
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Видно, что UserTransport является простым легким классом, содержащим базовую информацию о пользователе. Более сложным классом является GroupTransport, содержащий информацию об иерархии группы. Класс выглядит так:
public class GroupTransport
{
public class GroupUserAddedEventArgs : EventArgs
{
public List<UserTransport> Users { get; set; }
public GroupUserAddedEventArgs(List<UserTransport> users)
{
Users = users;
}
}
public List<GroupTransport> Unroll()
{
List<GroupTransport> retVal = new List<GroupTransport> {this};
foreach(GroupTransport child in Children)
{
retVal.AddRange(child.Unroll());
}
return retVal;
}
public string GroupName { get; set; }
public event EventHandler<GroupUserAddedEventArgs> UsersAdded;
private readonly List<GroupTransport> _groups = new List<GroupTransport>();
public List<GroupTransport> Children
{
get { return _groups; }
set
{
_groups.AddRange(value);
foreach (GroupTransport group in value)
{
group.UsersAdded += _GroupUsersAdded;
}
}
}
private void _GroupUsersAdded(object sender, GroupUserAddedEventArgs e)
{
if (e != null && e.Users.Count > 0)
{
_users.AddRange(e.Users);
if (UsersAdded != null)
{
UsersAdded(this, e);
}
}
}
private readonly List<UserTransport> _users = new List<UserTransport>();
public void AddUsers(List<UserTransport> users)
{
_users.AddRange(users);
if (UsersAdded != null)
{
UsersAdded(this, new GroupUserAddedEventArgs(users));
}
}
public List<UserTransport> Users
{
get { return _users; }
}
}
Важно отметить, что из-за пропуска стороны веб-приложения ("сервер" в модели клиент/сервер), в класс встроен некоторый бизнес-функционал, обычно существующий только на стороне сервера — Silverlight видел бы только свойства, а не методы для их заполнения.
Ключевыми свойствами являются имя группы (GroupName), пользователи, принадлежащие группе (Users), и подгруппы, принадлежащие группе (Children).
Остальные методы используются для заполнения иерархии. Событие добавления пользователя позволяет распространить до верха иерархии пользователя, добавленного ниже в иерархии. События развертывания выпрямляют иерархии в список, чтобы облегчить поиск группы в списке.